diff --git a/Marco.Pms.Services/Controllers/DirectoryController.cs b/Marco.Pms.Services/Controllers/DirectoryController.cs index 037838a..c496297 100644 --- a/Marco.Pms.Services/Controllers/DirectoryController.cs +++ b/Marco.Pms.Services/Controllers/DirectoryController.cs @@ -1,5 +1,4 @@ using Marco.Pms.Model.Dtos.Directory; -using Marco.Pms.Model.Utilities; using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; @@ -18,14 +17,16 @@ namespace Marco.Pms.Services.Controllers private readonly IDirectoryService _directoryService; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; + private readonly ISignalRService _signalR; private readonly Guid tenantId; - public DirectoryController(IDirectoryService directoryHelper, UserHelper userHelper, ILoggingService logger) + public DirectoryController(IDirectoryService directoryHelper, UserHelper userHelper, ILoggingService logger, ISignalRService signalR) { _directoryService = directoryHelper; _userHelper = userHelper; _logger = logger; tenantId = userHelper.GetTenantId(); + _signalR = signalR; } #region =================================================================== Contact APIs =================================================================== @@ -53,37 +54,16 @@ namespace Marco.Pms.Services.Controllers var response = await _directoryService.GetListOfContactsOld(search, active, filterDto, projectId); - if (response.StatusCode == 200) - { - return Ok(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); - } + return StatusCode(response.StatusCode, response); } [HttpGet("contact-bucket/{bucketId}")] public async Task GetContactsListByBucketId(Guid bucketId) { - var response = await _directoryService.GetContactsListByBucketId(bucketId); - if (response.StatusCode == 200) - { - return Ok(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); - } + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.GetContactsListByBucketIdAsync(bucketId, tenantId, loggedInEmployee); + return StatusCode(response.StatusCode, response); } [HttpGet("profile/{id}")] @@ -109,51 +89,42 @@ namespace Marco.Pms.Services.Controllers { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var response = await _directoryService.CreateContactAsync(createContact, tenantId, loggedInEmployee); + if (response.Success) + { + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); + } return StatusCode(response.StatusCode, response); } [HttpPut("{id}")] public async Task UpdateContact(Guid id, [FromBody] UpdateContactDto updateContact) { - var response = await _directoryService.UpdateContact(id, updateContact); - if (response.StatusCode == 200) + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.UpdateContactAsync(id, updateContact, tenantId, loggedInEmployee); + if (response.Success) { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } [HttpDelete("{id}")] - public async Task DeleteContact(Guid id, [FromQuery] bool? active) + public async Task DeleteContact(Guid id, [FromQuery] bool active = false) { - var response = await _directoryService.DeleteContact(id, active ?? false); - if (response.StatusCode == 200) + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.DeleteContactAsync(id, active, tenantId, loggedInEmployee); + if (response.Success) { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory", Response = id }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } #endregion - // -------------------------------- Contact Notes -------------------------------- + #region =================================================================== Contact Notes APIs =================================================================== [HttpGet("notes")] public async Task GetListOFAllNotes([FromQuery] Guid? projectId, [FromQuery] int? pageSize, [FromQuery] int pageNumber) @@ -224,122 +195,71 @@ namespace Marco.Pms.Services.Controllers return Ok(response); } - // -------------------------------- Bucket -------------------------------- + #endregion + + #region =================================================================== Bucket APIs =================================================================== [HttpGet("buckets")] public async Task GetBucketList() { - var response = await _directoryService.GetBucketList(); - if (response.StatusCode == 200) - { - return Ok(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); - } + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.GetBucketListAsync(tenantId, loggedInEmployee); + return StatusCode(response.StatusCode, response); } [HttpPost("bucket")] - public async Task CreateBucket(CreateBucketDto bucketDto) + public async Task CreateBucket([FromBody] CreateBucketDto bucketDto) { - if (!ModelState.IsValid) + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.CreateBucketAsync(bucketDto, tenantId, loggedInEmployee); + if (response.Success) { - var errors = ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); - _logger.LogWarning("User sent Invalid Date while marking attendance"); - return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); - } - var response = await _directoryService.CreateBucket(bucketDto); - if (response.StatusCode == 200) - { - return Ok(response); - } - else if (response.StatusCode == 409) - { - return Conflict(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory_Buckets", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } [HttpPut("bucket/{id}")] public async Task UpdateBucket(Guid id, [FromBody] UpdateBucketDto bucketDto) { - var response = await _directoryService.UpdateBucket(id, bucketDto); - if (response.StatusCode == 200) + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.UpdateBucketAsync(id, bucketDto, tenantId, loggedInEmployee); + if (response.Success) { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory_Buckets", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } [HttpPost("assign-bucket/{bucketId}")] public async Task AssignBucket(Guid bucketId, [FromBody] List assignBuckets) { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var response = await _directoryService.AssignBucket(bucketId, assignBuckets); - if (response.StatusCode == 200) + if (response.Success) { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory_Buckets", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } [HttpDelete("bucket/{id}")] public async Task DeleteBucket(Guid id) { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var response = await _directoryService.DeleteBucket(id); - if (response.StatusCode == 200) + if (response.Success) { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else if (response.StatusCode == 401) - { - return Unauthorized(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory_Buckets", Response = id }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } + + #endregion } } \ No newline at end of file diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index b6f5b4c..66e8a38 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -90,6 +90,7 @@ namespace Marco.Pms.Services.MappingProfiles CreateMap(); CreateMap(); + CreateMap(); CreateMap() .ForMember( diff --git a/Marco.Pms.Services/Service/DirectoryService.cs b/Marco.Pms.Services/Service/DirectoryService.cs index 2ff97b5..5c85fe9 100644 --- a/Marco.Pms.Services/Service/DirectoryService.cs +++ b/Marco.Pms.Services/Service/DirectoryService.cs @@ -261,7 +261,7 @@ namespace Marco.Pms.Services.Service else { _logger.LogWarning("Employee {EmployeeId} attemped to access a contacts, but do not have permission", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); + return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 403); } List filterbucketIds = bucketIds; @@ -385,246 +385,154 @@ namespace Marco.Pms.Services.Service } public async Task> GetContactsListByBucketIdAsync(Guid bucketId, Guid tenantId, Employee loggedInEmployee) { + // Validate incoming bucket ID if (bucketId == Guid.Empty) { - _logger.LogInfo("Employee ID {EmployeeId} sent an empty Bucket id", loggedInEmployee.Id); + _logger.LogInfo("Employee ID {EmployeeId} sent an empty Bucket ID.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Bucket ID is empty", "Bucket ID is empty", 400); } + // Check permissions of logged-in employee var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id); - if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission) { - // Log the specific denial reason for security auditing. - _logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get organization list for tenant {TenantId} due to lack of permissions.", loggedInEmployee.Id, tenantId); - // Return a strongly-typed error response. + _logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get contacts list for tenant {TenantId} due to insufficient permissions.", + loggedInEmployee.Id, tenantId); return ApiResponse.ErrorResponse("Access Denied", "You do not have permission to perform this action.", 403); } - Bucket? bucket = await _context.Buckets.FirstOrDefaultAsync(b => b.Id == bucketId && b.TenantId == tenantId); + // Use dbContextFactory to create new DbContext instances for parallel calls + using var context = _dbContextFactory.CreateDbContext(); + + // Confirm that the bucket exists and belongs to tenant + var bucket = await context.Buckets.FirstOrDefaultAsync(b => b.Id == bucketId && b.TenantId == tenantId); if (bucket == null) { - _logger.LogInfo("Employee ID {EmployeeId} attempted access to bucket ID {BucketId}, but not found in database", loggedInEmployee.Id); + _logger.LogInfo("Employee ID {EmployeeId} attempted to access non-existent bucket ID {BucketId}.", loggedInEmployee.Id, bucketId); return ApiResponse.ErrorResponse("Bucket not found", "Bucket not found", 404); } - List? employeeBuckets = await _context.EmployeeBucketMappings.Where(em => em.BucketId == bucketId).ToListAsync(); - EmployeeBucketMapping? employeeBucket = null; - if (hasAdminPermission) - { - employeeBucket = employeeBuckets.FirstOrDefault(); - } - else if (hasManagerPermission || hasUserPermission) - { - employeeBucket = employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == loggedInEmployee.Id); - } + // Load employee-bucket mappings for this bucket to check access + var employeeBuckets = await context.EmployeeBucketMappings.Where(em => em.BucketId == bucketId).ToListAsync(); + EmployeeBucketMapping? employeeBucket = hasAdminPermission + ? employeeBuckets.FirstOrDefault() + : employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == loggedInEmployee.Id); if (employeeBucket == null) { - _logger.LogInfo("Employee ID {EmployeeId} does not have access to bucket ID {BucketId}", loggedInEmployee.Id); - return ApiResponse.ErrorResponse("You do not have access to this bucket.", "You do not have access to this bucket.", 401); + _logger.LogInfo("Employee ID {EmployeeId} does not have access to bucket ID {BucketId}.", loggedInEmployee.Id, bucketId); + return ApiResponse.ErrorResponse("You do not have access to this bucket.", "Unauthorized access to bucket.", 403); } - List contactBucket = await _context.ContactBucketMappings.Where(cb => cb.BucketId == bucketId).ToListAsync() ?? new List(); - List contactVMs = new List(); - if (contactBucket.Count > 0) + // Fetch all contact-bucket mappings for this bucket + var contactBucketMappings = await context.ContactBucketMappings.Where(cb => cb.BucketId == bucketId).ToListAsync(); + + if (contactBucketMappings.Count == 0) { - var contactIds = contactBucket.Select(cb => cb.ContactId).ToList(); - List contacts = await _context.Contacts.Include(c => c.ContactCategory).Where(c => contactIds.Contains(c.Id) && c.IsActive).ToListAsync(); - List phones = await _context.ContactsPhones.Where(p => contactIds.Contains(p.ContactId)).ToListAsync(); - List emails = await _context.ContactsEmails.Where(e => contactIds.Contains(e.ContactId)).ToListAsync(); - - List? tags = await _context.ContactTagMappings.Where(ct => contactIds.Contains(ct.ContactId)).ToListAsync(); - List? contactProjects = await _context.ContactProjectMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync(); - List? contactBuckets = await _context.ContactBucketMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync(); - - List tagIds = new List(); - List tagMasters = new List(); - if (tags.Count > 0) - { - tagIds = tags.Select(ct => ct.ContactTagId).ToList(); - tagMasters = await _context.ContactTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync(); - } - - if (contacts.Count > 0) - { - - - foreach (var contact in contacts) - { - List? emailVMs = new List(); - List? phoneVMs = new List(); - List? tagVMs = new List(); - - List contactPhones = phones.Where(p => p.ContactId == contact.Id).ToList(); - List contactEmails = emails.Where(e => e.ContactId == contact.Id).ToList(); - - List? contactTags = tags.Where(t => t.ContactId == contact.Id).ToList(); - List? projectMappings = contactProjects.Where(cp => cp.ContactId == contact.Id).ToList(); - List? bucketMappings = contactBuckets.Where(cb => cb.ContactId == contact.Id).ToList(); - - if (contactPhones.Count > 0) - { - foreach (var phone in contactPhones) - { - ContactPhoneVM phoneVM = phone.ToContactPhoneVMFromContactPhone(); - phoneVMs.Add(phoneVM); - } - } - if (contactEmails.Count > 0) - { - foreach (var email in contactEmails) - { - ContactEmailVM emailVM = email.ToContactEmailVMFromContactEmail(); - emailVMs.Add(emailVM); - } - } - if (contactTags.Count > 0) - { - foreach (var contactTag in contactTags) - { - ContactTagMaster? tagMaster = tagMasters.Find(t => t.Id == contactTag.ContactTagId); - if (tagMaster != null) - { - ContactTagVM tagVM = tagMaster.ToContactTagVMFromContactTagMaster(); - tagVMs.Add(tagVM); - } - } - } - ContactVM contactVM = contact.ToContactVMFromContact(); - contactVM.ContactEmails = emailVMs; - contactVM.ContactPhones = phoneVMs; - contactVM.Tags = tagVMs; - contactVM.ProjectIds = projectMappings.Select(cp => cp.ProjectId).ToList(); - contactVM.BucketIds = bucketMappings.Select(cb => cb.BucketId).ToList(); - contactVMs.Add(contactVM); - } - } - + _logger.LogInfo("No contacts found in bucket ID {BucketId} for employee {EmployeeId}.", bucketId, loggedInEmployee.Id); + return ApiResponse.SuccessResponse(new List(), "No contacts available in this bucket.", 200); } - _logger.LogInfo("{count} contact from Bucket {BucketId} fetched by Employee {EmployeeId}", contactVMs.Count, bucketId, loggedInEmployee.Id); - return ApiResponse.SuccessResponse(contactVMs, $"{contactVMs.Count} contacts fetched successfully", 200); - } - public async Task> GetContactsListByBucketId(Guid id) - { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - if (id != Guid.Empty) + + var contactIds = contactBucketMappings.Select(cb => cb.ContactId).Distinct().ToList(); + + // Parallel fetching of related data via independent contexts to improve performance + var contactTask = Task.Run(async () => { - Bucket? bucket = await _context.Buckets.FirstOrDefaultAsync(b => b.Id == id && b.TenantId == tenantId); - if (bucket == null) - { - _logger.LogInfo("Employee ID {EmployeeId} attempted access to bucket ID {BucketId}, but not found in database", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("Bucket not found", "Bucket not found", 404); - } - List? employeeBuckets = await _context.EmployeeBucketMappings.Where(em => em.BucketId == id).ToListAsync(); - var assignedRoleIds = await _context.EmployeeRoleMappings.Where(r => r.EmployeeId == LoggedInEmployee.Id).Select(r => r.RoleId).ToListAsync(); - var permissionIds = await _context.RolePermissionMappings.Where(rp => assignedRoleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).Distinct().ToListAsync(); + await using var _dbContext = await _dbContextFactory.CreateDbContextAsync(); - EmployeeBucketMapping? employeeBucket = null; - if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin)) - { - employeeBucket = employeeBuckets.FirstOrDefault(); - } - else if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin) || permissionIds.Contains(PermissionsMaster.DirectoryUser)) - { - employeeBucket = employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == LoggedInEmployee.Id); - } - else - { - _logger.LogWarning("Employee {EmployeeId} attemped to access a contacts with in bucket {BucketId}, but do not have permission", LoggedInEmployee.Id, id); - return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); - } + return await _dbContext.Contacts.Include(c => c.ContactCategory) + .Where(c => contactIds.Contains(c.Id) && c.IsActive).ToListAsync(); + }); + var phoneTask = Task.Run(async () => + { + await using var _dbContext = await _dbContextFactory.CreateDbContextAsync(); - if (employeeBucket == null) - { - _logger.LogInfo("Employee ID {EmployeeId} does not have access to bucket ID {BucketId}", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("You do not have access to this bucket.", "You do not have access to this bucket.", 401); - } + return await _dbContext.ContactsPhones.Where(p => contactIds.Contains(p.ContactId)).ToListAsync(); + }); + var emailTask = Task.Run(async () => + { + await using var _dbContext = await _dbContextFactory.CreateDbContextAsync(); - List contactBucket = await _context.ContactBucketMappings.Where(cb => cb.BucketId == id).ToListAsync() ?? new List(); - List contactVMs = new List(); - if (contactBucket.Count > 0) - { - var contactIds = contactBucket.Select(cb => cb.ContactId).ToList(); - List contacts = await _context.Contacts.Include(c => c.ContactCategory).Where(c => contactIds.Contains(c.Id) && c.IsActive).ToListAsync(); - List phones = await _context.ContactsPhones.Where(p => contactIds.Contains(p.ContactId)).ToListAsync(); - List emails = await _context.ContactsEmails.Where(e => contactIds.Contains(e.ContactId)).ToListAsync(); + return await _dbContext.ContactsEmails.Where(e => contactIds.Contains(e.ContactId)).ToListAsync(); + }); + var tagTask = Task.Run(async () => + { + await using var _dbContext = await _dbContextFactory.CreateDbContextAsync(); - List? tags = await _context.ContactTagMappings.Where(ct => contactIds.Contains(ct.ContactId)).ToListAsync(); - List? contactProjects = await _context.ContactProjectMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync(); - List? contactBuckets = await _context.ContactBucketMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync(); + return await _dbContext.ContactTagMappings.Where(ct => contactIds.Contains(ct.ContactId)).ToListAsync(); + }); + var contactProjectTask = Task.Run(async () => + { + await using var _dbContext = await _dbContextFactory.CreateDbContextAsync(); - List tagIds = new List(); - List tagMasters = new List(); - if (tags.Count > 0) - { - tagIds = tags.Select(ct => ct.ContactTagId).ToList(); - tagMasters = await _context.ContactTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync(); - } + return await _dbContext.ContactProjectMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync(); + }); + var contactBucketTask = Task.Run(async () => + { + await using var _dbContext = await _dbContextFactory.CreateDbContextAsync(); - if (contacts.Count > 0) - { + return await _dbContext.ContactBucketMappings.Where(cb => contactIds.Contains(cb.ContactId)).ToListAsync(); + }); + await Task.WhenAll(contactTask); - foreach (var contact in contacts) - { - List? emailVMs = new List(); - List? phoneVMs = new List(); - List? tagVMs = new List(); + var contacts = contactTask.Result; + var phones = phoneTask.Result; + var emails = emailTask.Result; + var tags = tagTask.Result; + var contactProjects = contactProjectTask.Result; + var contactBuckets = contactBucketTask.Result; - List contactPhones = phones.Where(p => p.ContactId == contact.Id).ToList(); - List contactEmails = emails.Where(e => e.ContactId == contact.Id).ToList(); - - List? contactTags = tags.Where(t => t.ContactId == contact.Id).ToList(); - List? projectMappings = contactProjects.Where(cp => cp.ContactId == contact.Id).ToList(); - List? bucketMappings = contactBuckets.Where(cb => cb.ContactId == contact.Id).ToList(); - - if (contactPhones.Count > 0) - { - foreach (var phone in contactPhones) - { - ContactPhoneVM phoneVM = phone.ToContactPhoneVMFromContactPhone(); - phoneVMs.Add(phoneVM); - } - } - if (contactEmails.Count > 0) - { - foreach (var email in contactEmails) - { - ContactEmailVM emailVM = email.ToContactEmailVMFromContactEmail(); - emailVMs.Add(emailVM); - } - } - if (contactTags.Count > 0) - { - foreach (var contactTag in contactTags) - { - ContactTagMaster? tagMaster = tagMasters.Find(t => t.Id == contactTag.ContactTagId); - if (tagMaster != null) - { - ContactTagVM tagVM = tagMaster.ToContactTagVMFromContactTagMaster(); - tagVMs.Add(tagVM); - } - } - } - ContactVM contactVM = contact.ToContactVMFromContact(); - contactVM.ContactEmails = emailVMs; - contactVM.ContactPhones = phoneVMs; - contactVM.Tags = tagVMs; - contactVM.ProjectIds = projectMappings.Select(cp => cp.ProjectId).ToList(); - contactVM.BucketIds = bucketMappings.Select(cb => cb.BucketId).ToList(); - contactVMs.Add(contactVM); - } - } - - } - _logger.LogInfo("{count} contact from Bucket {BucketId} fetched by Employee {EmployeeId}", contactVMs.Count, id, LoggedInEmployee.Id); - return ApiResponse.SuccessResponse(contactVMs, $"{contactVMs.Count} contacts fetched successfully", 200); + // Load tag metadata if tags found + List tagMasters = new List(); + if (tags.Count > 0) + { + var tagIds = tags.Select(t => t.ContactTagId).Distinct().ToList(); + tagMasters = await context.ContactTagMasters.Where(tm => tagIds.Contains(tm.Id)).ToListAsync(); } - _logger.LogInfo("Employee ID {EmployeeId} sent an empty Bucket id", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("Bucket ID is empty", "Bucket ID is empty", 400); + + var contactVMs = new List(); + + // Build contact view models from data + foreach (var contact in contacts) + { + // Transform phones + var phoneVMs = phones.Where(p => p.ContactId == contact.Id).Select(p => _mapper.Map(p)).ToList(); + + // Transform emails + var emailVMs = emails.Where(e => e.ContactId == contact.Id).Select(e => _mapper.Map(e)).ToList(); + + // Transform tags + var contactTagMappings = tags.Where(t => t.ContactId == contact.Id); + var tagVMs = new List(); + foreach (var ct in contactTagMappings) + { + var tagMaster = tagMasters.Find(tm => tm.Id == ct.ContactTagId); + if (tagMaster != null) + { + tagVMs.Add(_mapper.Map(tagMaster)); + } + } + + // Get project and bucket mappings for the contact + var projectIds = contactProjects.Where(cp => cp.ContactId == contact.Id).Select(cp => cp.ProjectId).ToList(); + var bucketIds = contactBuckets.Where(cb => cb.ContactId == contact.Id).Select(cb => cb.BucketId).ToList(); + + // Create the contact VM + var contactVM = _mapper.Map(contact); + contactVM.ContactPhones = phoneVMs; + contactVM.ContactEmails = emailVMs; + contactVM.Tags = tagVMs; + contactVM.ProjectIds = projectIds; + contactVM.BucketIds = bucketIds; + + contactVMs.Add(contactVM); + } + + _logger.LogInfo("{Count} contacts from Bucket {BucketId} fetched successfully by Employee {EmployeeId}.", contactVMs.Count, bucketId, loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(contactVMs, $"{contactVMs.Count} contacts fetched successfully.", 200); } public async Task> GetContactProfileAsync(Guid id, Guid tenantId, Employee loggedInEmployee) { @@ -933,319 +841,399 @@ namespace Marco.Pms.Services.Service #endregion #region =================================================================== Contact Put APIs =================================================================== - public async Task> UpdateContact(Guid id, UpdateContactDto updateContact) + + public async Task> UpdateContactAsync(Guid id, UpdateContactDto updateContact, Guid tenantId, Employee loggedInEmployee) { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - if (updateContact != null) + if (updateContact == null) { - if (updateContact.Id != id) + _logger.LogWarning("Employee {EmployeeId} sent empty payload for updating contact.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Empty payload", "User sent empty payload", 400); + } + + // Ensure payload ID matches path ID + if (updateContact.Id != id) + { + _logger.LogWarning("Employee {EmployeeId} sent mismatched contact IDs. Path: {PathId}, Payload: {PayloadId}", + loggedInEmployee.Id, id, updateContact.Id); + return ApiResponse.ErrorResponse("Invalid data", "Payload ID does not match path parameter", 400); + } + + using var context = _dbContextFactory.CreateDbContext(); + + // Retrieve the contact with tracking disabled for initial existence and permission check + var contact = await context.Contacts + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId); + + if (contact == null) + { + _logger.LogWarning("Employee {EmployeeId} attempted to update non-existing contact {ContactId}", + loggedInEmployee.Id, id); + return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); + } + + // Validate permissions + var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id); + + // Determine accessible bucket IDs for this employee + List bucketIds; + if (hasAdminPermission) + { + bucketIds = await context.Buckets + .Where(b => b.TenantId == tenantId) + .Select(b => b.Id) + .ToListAsync(); + } + else if (hasManagerPermission || hasUserPermission) + { + var employeeBucketIds = await context.EmployeeBucketMappings + .Where(eb => eb.EmployeeId == loggedInEmployee.Id) + .Select(eb => eb.BucketId) + .ToListAsync(); + + var createdBucketIds = await context.Buckets + .Where(b => b.CreatedByID == loggedInEmployee.Id) + .Select(b => b.Id) + .ToListAsync(); + + bucketIds = employeeBucketIds.Concat(createdBucketIds).Distinct().ToList(); + } + else + { + _logger.LogWarning("Employee {EmployeeId} does not have permission to update contact {ContactId}", + loggedInEmployee.Id, id); + return ApiResponse.ErrorResponse("Unauthorized", "You do not have permission", 403); + } + + // Fetch contact bucket mappings accessible by the user + var contactBuckets = await context.ContactBucketMappings + .Where(cb => cb.ContactId == contact.Id && bucketIds.Contains(cb.BucketId)) + .ToListAsync(); + + // Refresh bucket IDs to only those relevant to this contact & permissions + var accessibleBucketIds = contactBuckets.Select(cb => cb.BucketId).Distinct().ToHashSet(); + + // Update the main contact object from DTO + var updatedContact = updateContact.ToContactFromUpdateContactDto(tenantId, contact); + updatedContact.UpdatedById = loggedInEmployee.Id; + updatedContact.UpdatedAt = DateTime.UtcNow; + + // Attach updated contact (tracked entity) + context.Contacts.Update(updatedContact); + + // Prepare parallel tasks for retrieving related collections in independent DbContext instances + var phonesTask = Task.Run(async () => + { + using var ctx = _dbContextFactory.CreateDbContext(); + return await ctx.ContactsPhones.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync(); + }); + + var emailsTask = Task.Run(async () => + { + using var ctx = _dbContextFactory.CreateDbContext(); + return await ctx.ContactsEmails.AsNoTracking().Where(e => e.ContactId == contact.Id).ToListAsync(); + }); + + var tagsTask = Task.Run(async () => + { + using var ctx = _dbContextFactory.CreateDbContext(); + return await ctx.ContactTagMappings.AsNoTracking().Where(t => t.ContactId == contact.Id).ToListAsync(); + }); + + var projectsTask = Task.Run(async () => + { + using var ctx = _dbContextFactory.CreateDbContext(); + return await ctx.ContactProjectMappings.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync(); + }); + + // Await all tasks to complete + await Task.WhenAll(phonesTask, emailsTask, tagsTask, projectsTask); + + var phones = phonesTask.Result; + var emails = emailsTask.Result; + var contactTags = tagsTask.Result; + var contactProjects = projectsTask.Result; + + var phoneIds = phones.Select(p => p.Id).ToHashSet(); + var emailIds = emails.Select(e => e.Id).ToHashSet(); + var tagIds = contactTags.Select(t => t.ContactTagId).Distinct().ToHashSet(); + var projectIds = contactProjects.Select(p => p.ProjectId).Distinct().ToHashSet(); + + // Fetch all tags for this tenant for name checks + var allTags = await context.ContactTagMasters.Where(t => t.TenantId == tenantId).ToListAsync(); + var tagNameLookup = allTags.ToDictionary(t => t.Name.ToLowerInvariant(), t => t); + + // ---------------------- Update Phones ----------------------- + if (updateContact.ContactPhones != null) + { + var updatedPhoneIds = updateContact.ContactPhones.Select(p => p.Id).Where(id => id != null && id != Guid.Empty).ToHashSet(); + + foreach (var phoneDto in updateContact.ContactPhones) { - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended different ID in payload and path parameter", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("Invalid data", "Invalid data", 400); - } - Contact? contact = await _context.Contacts.AsNoTracking().FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId); - if (contact == null) - { - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} tries to update contact with ID {ContactId} is not found in database", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); + var phoneEntity = phoneDto.ToContactPhoneFromUpdateContactPhoneDto(tenantId, contact.Id); + if (phoneDto.Id != null && phoneDto.Id != Guid.Empty && phoneIds.Contains(phoneEntity.Id)) + context.ContactsPhones.Update(phoneEntity); + else + context.ContactsPhones.Add(phoneEntity); } - var assignedRoleIds = await _context.EmployeeRoleMappings.Where(r => r.EmployeeId == LoggedInEmployee.Id).Select(r => r.RoleId).ToListAsync(); - var permissionIds = await _context.RolePermissionMappings.Where(rp => assignedRoleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).Distinct().ToListAsync(); - List? employeeBuckets = await _context.EmployeeBucketMappings.Where(eb => eb.EmployeeId == LoggedInEmployee.Id).ToListAsync(); - List bucketIds = employeeBuckets.Select(c => c.BucketId).ToList(); - if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin)) - { - var buckets = await _context.Buckets.Where(b => b.TenantId == tenantId).ToListAsync(); - bucketIds = buckets.Select(b => b.Id).ToList(); - } - else if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin) || permissionIds.Contains(PermissionsMaster.DirectoryUser)) - { - var buckets = await _context.Buckets.Where(b => b.CreatedByID == LoggedInEmployee.Id).ToListAsync(); - var createdBucketIds = buckets.Select(b => b.Id).ToList(); - bucketIds.AddRange(createdBucketIds); - bucketIds = bucketIds.Distinct().ToList(); - } - else - { - _logger.LogWarning("Employee {EmployeeId} attemped to update a contact, but do not have permission", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); - } - - List contactBuckets = await _context.ContactBucketMappings.AsNoTracking().Where(m => m.ContactId == contact.Id && bucketIds.Contains(m.BucketId)).ToListAsync(); - bucketIds = contactBuckets.Select(b => b.BucketId).Distinct().ToList(); - - - - var newContact = updateContact.ToContactFromUpdateContactDto(tenantId, contact); - newContact.UpdatedById = LoggedInEmployee.Id; - newContact.UpdatedAt = DateTime.UtcNow; - _context.Contacts.Update(newContact); - await _context.SaveChangesAsync(); - - List phones = await _context.ContactsPhones.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync(); - var phoneIds = phones.Select(p => p.Id).ToList(); - List emails = await _context.ContactsEmails.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync(); - var emailIds = emails.Select(p => p.Id).ToList(); - - - - List contactTags = await _context.ContactTagMappings.AsNoTracking().Where(m => m.ContactId == contact.Id).ToListAsync(); - var tagIds = contactTags.Select(t => t.ContactTagId).Distinct().ToList(); - - - List contactProjects = await _context.ContactProjectMappings.AsNoTracking().Where(m => m.ContactId == contact.Id).ToListAsync(); - var projectIds = contactProjects.Select(t => t.ProjectId).Distinct().ToList(); - - List tags = await _context.ContactTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync(); - List allTags = await _context.ContactTagMasters.Where(t => t.TenantId == tenantId).ToListAsync(); - var tagNames = allTags.Select(t => t.Name.ToLower()).ToList(); - - if (updateContact.ContactPhones != null) - { - var updatedPhoneIds = updateContact.ContactPhones.Select(p => p.Id).Distinct().ToList(); - foreach (var phoneDto in updateContact.ContactPhones) - { - var phone = phoneDto.ToContactPhoneFromUpdateContactPhoneDto(tenantId, contact.Id); - if (phoneDto.Id != null && phoneDto.Id != Guid.Empty && phoneIds.Contains(phone.Id)) - { - _context.ContactsPhones.Update(phone); - } - else - { - _context.ContactsPhones.Add(phone); - } - } - foreach (var phone in phones) - { - - if (!updatedPhoneIds.Contains(phone.Id)) - { - _context.ContactsPhones.Remove(phone); - } - } - } - else if (phones != null) - { - _context.ContactsPhones.RemoveRange(phones); - } - - if (updateContact.ContactEmails != null) - { - var updateEmailIds = updateContact.ContactEmails.Select(p => p.Id).Distinct().ToList(); - - foreach (var emailDto in updateContact.ContactEmails) - { - var email = emailDto.ToContactEmailFromUpdateContactEmailDto(tenantId, contact.Id); - if (emailDto.Id != null && emailDto.Id != Guid.Empty && emailIds.Contains(email.Id)) - { - _context.ContactsEmails.Update(email); - } - else - { - _context.ContactsEmails.Add(email); - } - } - foreach (var email in emails) - { - - if (!updateEmailIds.Contains(email.Id)) - { - _context.ContactsEmails.Remove(email); - } - } - } - else if (emails != null) - { - _context.ContactsEmails.RemoveRange(emails); - } - - if (updateContact.BucketIds != null) - { - foreach (var bucketId in updateContact.BucketIds) - { - if (!bucketIds.Contains(bucketId)) - { - _context.ContactBucketMappings.Add(new ContactBucketMapping - { - BucketId = bucketId, - ContactId = contact.Id - }); - } - } - foreach (var bucketMapping in contactBuckets) - { - if (!updateContact.BucketIds.Contains(bucketMapping.BucketId)) - { - _context.ContactBucketMappings.Remove(bucketMapping); - } - } - } - else if (contactBuckets != null) - { - _context.ContactBucketMappings.RemoveRange(contactBuckets); - } - - if (updateContact.ProjectIds != null) - { - foreach (var ProjectId in updateContact.ProjectIds) - { - if (!projectIds.Contains(ProjectId)) - { - _context.ContactProjectMappings.Add(new ContactProjectMapping - { - ProjectId = ProjectId, - ContactId = contact.Id, - TenantId = tenantId - }); - } - } - - foreach (var projectMapping in contactProjects) - { - if (!updateContact.ProjectIds.Contains(projectMapping.ProjectId)) - { - _context.ContactProjectMappings.Remove(projectMapping); - } - } - } - else if (contactProjects != null) - { - _context.ContactProjectMappings.RemoveRange(contactProjects); - } - - if (updateContact.Tags != null) - { - var updatedTagIds = updateContact.Tags.Select(t => t.Id).Distinct().ToList(); - foreach (var tag in updateContact.Tags) - { - var namecheck = tagNames.Contains(tag.Name.ToLower()); - var idCheck = (!tagIds.Contains(tag.Id ?? Guid.Empty)); - var test = namecheck && idCheck; - if (test) - { - ContactTagMaster existingTag = tags.Find(t => t.Name == tag.Name) ?? new ContactTagMaster(); - _context.ContactTagMappings.Add(new ContactTagMapping - { - ContactId = contact.Id, - ContactTagId = tag.Id ?? existingTag.Id - }); - } - else if (tag.Id == null || tag.Id == Guid.Empty) - { - ContactTagMaster contactTag = new ContactTagMaster - { - Name = tag.Name, - Description = "", - TenantId = tenantId - }; - _context.ContactTagMasters.Add(contactTag); - - _context.ContactTagMappings.Add(new ContactTagMapping - { - ContactId = contact.Id, - ContactTagId = contactTag.Id - }); - } - } - foreach (var contactTag in contactTags) - { - if (!updatedTagIds.Contains(contactTag.ContactTagId)) - { - _context.ContactTagMappings.Remove(contactTag); - } - } - } - else if (contactTags != null) - { - _context.ContactTagMappings.RemoveRange(contactTags); - } - _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog - { - RefereanceId = contact.Id, - UpdatedById = LoggedInEmployee.Id, - UpdateAt = DateTime.UtcNow - }); - - await _context.SaveChangesAsync(); - - contact = await _context.Contacts.Include(c => c.ContactCategory).FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId) ?? new Contact(); - phones = await _context.ContactsPhones.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync(); - emails = await _context.ContactsEmails.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync(); - contactTags = await _context.ContactTagMappings.AsNoTracking().Where(m => m.ContactId == contact.Id).ToListAsync(); - contactBuckets = await _context.ContactBucketMappings.AsNoTracking().Where(cb => cb.ContactId == contact.Id).ToListAsync(); - contactProjects = await _context.ContactProjectMappings.AsNoTracking().Where(cp => cp.ContactId == contact.Id).ToListAsync(); - tagIds = contactTags.Select(t => t.ContactTagId).Distinct().ToList(); - tags = await _context.ContactTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync(); - - ContactVM contactVM = new ContactVM(); - List phoneVMs = new List(); + // Remove phones not updated in payload foreach (var phone in phones) { - ContactPhoneVM phoneVM = phone.ToContactPhoneVMFromContactPhone(); - phoneVMs.Add(phoneVM); + if (!updatedPhoneIds.Contains(phone.Id)) + { + context.ContactsPhones.Remove(phone); + } } - List emailVMs = new List(); + } + else if (phones.Any()) + { + context.ContactsPhones.RemoveRange(phones); + } + + // ---------------------- Update Emails ----------------------- + if (updateContact.ContactEmails != null) + { + var updatedEmailIds = updateContact.ContactEmails.Select(e => e.Id).Where(id => id != null && id != Guid.Empty).ToHashSet(); + + foreach (var emailDto in updateContact.ContactEmails) + { + var emailEntity = emailDto.ToContactEmailFromUpdateContactEmailDto(tenantId, contact.Id); + if (emailDto.Id != null && emailDto.Id != Guid.Empty && emailIds.Contains(emailEntity.Id)) + context.ContactsEmails.Update(emailEntity); + else + context.ContactsEmails.Add(emailEntity); + } + + // Remove emails not updated in payload foreach (var email in emails) { - ContactEmailVM emailVM = email.ToContactEmailVMFromContactEmail(); - emailVMs.Add(emailVM); + if (!updatedEmailIds.Contains(email.Id)) + { + context.ContactsEmails.Remove(email); + } } - List tagVMs = new List(); + } + else if (emails.Any()) + { + context.ContactsEmails.RemoveRange(emails); + } + + // ---------------------- Update Buckets ----------------------- + if (updateContact.BucketIds != null) + { + var incomingBucketIds = updateContact.BucketIds.ToHashSet(); + + // Add newly added bucket mappings if accessible by user + foreach (var bucketId in incomingBucketIds) + { + if (!accessibleBucketIds.Contains(bucketId) && bucketIds.Contains(bucketId)) + { + context.ContactBucketMappings.Add(new ContactBucketMapping + { + BucketId = bucketId, + ContactId = contact.Id + }); + } + } + + // Remove bucket mappings removed in payload + foreach (var contactBucket in contactBuckets) + { + if (!incomingBucketIds.Contains(contactBucket.BucketId)) + { + context.ContactBucketMappings.Remove(contactBucket); + } + } + } + else if (contactBuckets.Any()) + { + context.ContactBucketMappings.RemoveRange(contactBuckets); + } + + // ---------------------- Update Projects ----------------------- + if (updateContact.ProjectIds != null) + { + var incomingProjectIds = updateContact.ProjectIds.ToHashSet(); + + foreach (var projectId in incomingProjectIds) + { + if (!projectIds.Contains(projectId)) + { + context.ContactProjectMappings.Add(new ContactProjectMapping + { + ProjectId = projectId, + ContactId = contact.Id, + TenantId = tenantId + }); + } + } + + foreach (var projectMapping in contactProjects) + { + if (!incomingProjectIds.Contains(projectMapping.ProjectId)) + { + context.ContactProjectMappings.Remove(projectMapping); + } + } + } + else if (contactProjects.Any()) + { + context.ContactProjectMappings.RemoveRange(contactProjects); + } + + // ---------------------- Update Tags ----------------------- + if (updateContact.Tags != null) + { + var updatedTagIds = updateContact.Tags.Select(t => t.Id).Where(id => id != null && id != Guid.Empty).ToHashSet(); + + foreach (var tagDto in updateContact.Tags) + { + var lowerName = tagDto.Name.Trim().ToLowerInvariant(); + + bool existsByName = !string.IsNullOrWhiteSpace(lowerName) && tagNameLookup.ContainsKey(lowerName); + bool idNotExistsInMapping = !updatedTagIds.Contains(tagDto.Id ?? Guid.Empty); + + if (existsByName && idNotExistsInMapping) + { + // Use existing tag by name + var existingTag = tagNameLookup[lowerName]; + context.ContactTagMappings.Add(new ContactTagMapping + { + ContactId = contact.Id, + ContactTagId = tagDto.Id ?? existingTag.Id + }); + } + else if (tagDto.Id == null || tagDto.Id == Guid.Empty) + { + // Create new tag master and mapping + var newTagMaster = new ContactTagMaster + { + Name = tagDto.Name, + Description = tagDto.Name, + TenantId = tenantId + }; + context.ContactTagMasters.Add(newTagMaster); + + // Mapping will use newTagMaster.Id once saved (EF will fix after SaveChanges) + context.ContactTagMappings.Add(new ContactTagMapping + { + ContactId = contact.Id, + ContactTagId = newTagMaster.Id + }); + } + } + + // Remove tag mappings no longer present foreach (var contactTagMapping in contactTags) { - ContactTagVM tagVM = new ContactTagVM(); - var tag = tags.Find(t => t.Id == contactTagMapping.ContactTagId); - tagVM = tag != null ? tag.ToContactTagVMFromContactTagMaster() : new ContactTagVM(); - tagVMs.Add(tagVM); + if (!updatedTagIds.Contains(contactTagMapping.ContactTagId)) + { + context.ContactTagMappings.Remove(contactTagMapping); + } } - - - contactVM = contact.ToContactVMFromContact(); - contactVM.ContactPhones = phoneVMs; - contactVM.ContactEmails = emailVMs; - contactVM.Tags = tagVMs; - contactVM.BucketIds = contactBuckets.Select(cb => cb.BucketId).ToList(); - contactVM.ProjectIds = contactProjects.Select(cp => cp.ProjectId).ToList(); - - _logger.LogInfo("Conatct {ContactId} has been updated by employee {EmployeeId}", contact.Id, LoggedInEmployee.Id); - return ApiResponse.SuccessResponse(contactVM, "Contact Updated Successfully", 200); } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400); + else if (contactTags.Any()) + { + context.ContactTagMappings.RemoveRange(contactTags); + } + + // ---------------------- Add Update Log ----------------------- + context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog + { + RefereanceId = contact.Id, + UpdatedById = loggedInEmployee.Id, + UpdateAt = DateTime.UtcNow + }); + + // Save all changes once here + try + { + await context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException ex) + { + _logger.LogError(ex, "Concurrency conflict while employee {EmployeeId} was updating contact {ContactId}", loggedInEmployee.Id, contact.Id); + return ApiResponse.ErrorResponse("Concurrency conflict", "This contact was updated by another user. Please reload and try again.", 409); + } + + // Reload updated contact and related data for response, using a fresh context + using var responseContext = _dbContextFactory.CreateDbContext(); + + var reloadedContact = await responseContext.Contacts + .Include(c => c.ContactCategory) + .FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId) ?? new Contact(); + + var responsePhones = await responseContext.ContactsPhones.AsNoTracking().Where(p => p.ContactId == reloadedContact.Id).ToListAsync(); + var responseEmails = await responseContext.ContactsEmails.AsNoTracking().Where(e => e.ContactId == reloadedContact.Id).ToListAsync(); + var responseContactTags = await responseContext.ContactTagMappings.AsNoTracking().Where(t => t.ContactId == reloadedContact.Id).ToListAsync(); + var responseContactBuckets = await responseContext.ContactBucketMappings.AsNoTracking().Where(cb => cb.ContactId == reloadedContact.Id).ToListAsync(); + var responseContactProjects = await responseContext.ContactProjectMappings.AsNoTracking().Where(cp => cp.ContactId == reloadedContact.Id).ToListAsync(); + + var tagIdsForResponse = responseContactTags.Select(t => t.ContactTagId).Distinct().ToList(); + var tagsForResponse = await responseContext.ContactTagMasters.Where(t => tagIdsForResponse.Contains(t.Id)).ToListAsync(); + + // Map entities to view models + var contactVM = reloadedContact.ToContactVMFromContact(); + + contactVM.ContactPhones = responsePhones.Select(p => p.ToContactPhoneVMFromContactPhone()).ToList(); + contactVM.ContactEmails = responseEmails.Select(e => e.ToContactEmailVMFromContactEmail()).ToList(); + contactVM.Tags = responseContactTags.Select(ctm => + { + var tag = tagsForResponse.Find(t => t.Id == ctm.ContactTagId); + return tag != null ? tag.ToContactTagVMFromContactTagMaster() : new ContactTagVM(); + }).ToList(); + + contactVM.BucketIds = responseContactBuckets.Select(cb => cb.BucketId).ToList(); + contactVM.ProjectIds = responseContactProjects.Select(cp => cp.ProjectId).ToList(); + + _logger.LogInfo("Contact {ContactId} successfully updated by employee {EmployeeId}.", contact.Id, loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(contactVM, "Contact Updated Successfully", 200); } + #endregion #region =================================================================== Contact Delete APIs =================================================================== - public async Task> DeleteContact(Guid id, bool active) + + public async Task> DeleteContactAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee) { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - if (id != Guid.Empty) + // Validate the contact id + if (id == Guid.Empty) { - Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); - if (contact == null) - { - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} tries to delete contact with ID {ContactId} is not found in database", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); - } - contact.IsActive = active; - - _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog - { - RefereanceId = contact.Id, - UpdatedById = LoggedInEmployee.Id, - UpdateAt = DateTime.UtcNow - }); - - await _context.SaveChangesAsync(); - _logger.LogInfo("Contact {ContactId} has been deleted by Employee {Employee}", id, LoggedInEmployee.Id); - return ApiResponse.SuccessResponse(new { }, "Contact is deleted Successfully", 200); + _logger.LogWarning("Employee ID {EmployeeId} attempted to delete with an empty contact ID.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400); } - _logger.LogInfo("Employee ID {EmployeeId} sent an empty contact id", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400); + + // Try to find the contact for the given tenant + Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); + + if (contact == null) + { + _logger.LogWarning("Employee ID {EmployeeId} attempted to delete contact ID {ContactId}, but it was not found.", loggedInEmployee.Id, id); + return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); + } + + // Update the contact's active status (soft delete or activate) + contact.IsActive = active; + + // Log the update in the directory update logs + _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog + { + RefereanceId = contact.Id, + UpdatedById = loggedInEmployee.Id, + UpdateAt = DateTime.UtcNow + }); + + // Save changes to the database + await _context.SaveChangesAsync(); + + _logger.LogInfo("Contact ID {ContactId} has been {(DeletedOrActivated)} by Employee ID {EmployeeId}.", id, active ? "activated" : "deleted", loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(new { }, active ? "Contact is activated successfully" : "Contact is deleted successfully", 200); } + + #endregion #endregion @@ -1269,7 +1257,7 @@ namespace Marco.Pms.Services.Service if (loggedInEmployee == null) { _logger.LogWarning("GetListOFAllNotes: LoggedInEmployee is null. Cannot proceed."); - return ApiResponse.ErrorResponse("Unauthorized", "Employee not found.", 401); + return ApiResponse.ErrorResponse("Unauthorized", "Employee not found.", 403); } // --- Permission Checks --- @@ -1510,171 +1498,288 @@ namespace Marco.Pms.Services.Service #endregion #region =================================================================== Bucket APIs =================================================================== - - public async Task> GetBucketList() + public async Task> GetBucketListAsync(Guid tenantId, Employee loggedInEmployee) { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - var assignedRoleIds = await _context.EmployeeRoleMappings.Where(r => r.EmployeeId == LoggedInEmployee.Id).Select(r => r.RoleId).ToListAsync(); - var permissionIds = await _context.RolePermissionMappings.Where(rp => assignedRoleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).Distinct().ToListAsync(); + _logger.LogInfo("Started fetching bucket list for Employee {EmployeeId} in Tenant {TenantId}", loggedInEmployee.Id, tenantId); - List employeeBuckets = await _context.EmployeeBucketMappings.Where(b => b.EmployeeId == LoggedInEmployee.Id).ToListAsync(); - var bucketIds = employeeBuckets.Select(b => b.BucketId).ToList(); + // Check permissions early + var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id); - List bucketList = new List(); - if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin)) + if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission) { - bucketList = await _context.Buckets.Include(b => b.CreatedBy).Where(b => b.TenantId == tenantId).ToListAsync(); - bucketIds = bucketList.Select(b => b.Id).ToList(); + _logger.LogWarning("Employee {EmployeeId} attempted to access buckets without permission", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 403); } - else if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin) || permissionIds.Contains(PermissionsMaster.DirectoryUser)) + + List bucketList; + List bucketIds; + + if (hasAdminPermission) { - bucketList = await _context.Buckets.Include(b => b.CreatedBy).Where(b => bucketIds.Contains(b.Id) || b.CreatedByID == LoggedInEmployee.Id).ToListAsync(); + // Admin gets all buckets for the tenant + bucketList = await _context.Buckets + .Include(b => b.CreatedBy) + .Where(b => b.TenantId == tenantId) + .ToListAsync(); + + bucketIds = bucketList.Select(b => b.Id).ToList(); } else { - _logger.LogWarning("Employee {EmployeeId} attemped to access a buckets list, but do not have permission", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); - } + // Manager or user: fetch employee bucket mappings and buckets accordingly - List employeeBucketVM = await _context.EmployeeBucketMappings.Where(b => bucketIds.Contains(b.BucketId)).ToListAsync(); + var employeeBuckets = await _context.EmployeeBucketMappings + .Where(b => b.EmployeeId == loggedInEmployee.Id) + .ToListAsync(); + + bucketIds = employeeBuckets.Select(b => b.BucketId).ToList(); + + bucketList = await _context.Buckets + .Include(b => b.CreatedBy) + .Where(b => bucketIds.Contains(b.Id) || b.CreatedByID == loggedInEmployee.Id) + .ToListAsync(); - List bucketVMs = new List(); - if (bucketList.Any()) - { bucketIds = bucketList.Select(b => b.Id).ToList(); - List? contactBucketMappings = await _context.ContactBucketMappings.Where(cb => bucketIds.Contains(cb.BucketId)).ToListAsync(); - foreach (var bucket in bucketList) - { - List employeeBucketMappings = employeeBucketVM.Where(eb => eb.BucketId == bucket.Id).ToList(); - var emplyeeIds = employeeBucketMappings.Select(eb => eb.EmployeeId).ToList(); - List? contactBuckets = contactBucketMappings.Where(cb => cb.BucketId == bucket.Id).ToList(); - AssignBucketVM bucketVM = bucket.ToAssignBucketVMFromBucket(); - if (bucketVM.CreatedBy != null) - { - emplyeeIds.Add(bucketVM.CreatedBy.Id); - } - bucketVM.EmployeeIds = emplyeeIds.Distinct().ToList(); - bucketVM.NumberOfContacts = contactBuckets.Count; - bucketVMs.Add(bucketVM); - } } - _logger.LogInfo("{count} Buckets are fetched by Employee with ID {LoggedInEmployeeId}", bucketVMs.Count, LoggedInEmployee.Id); + if (!bucketList.Any()) + { + _logger.LogInfo("No buckets found for Employee {EmployeeId}", loggedInEmployee.Id); + return ApiResponse.SuccessResponse(new List(), "No buckets found", 200); + } + + // Fetch related data in parallel using Task.Run with separate contexts to avoid concurrency issues + var employeeBucketMappingsTask = Task.Run(async () => + { + using var context = _dbContextFactory.CreateDbContext(); + return await context.EmployeeBucketMappings + .Where(b => bucketIds.Contains(b.BucketId)) + .ToListAsync(); + }); + + var contactBucketMappingsTask = Task.Run(async () => + { + using var context = _dbContextFactory.CreateDbContext(); + return await context.ContactBucketMappings + .Where(cb => bucketIds.Contains(cb.BucketId)) + .ToListAsync(); + }); + + await Task.WhenAll(employeeBucketMappingsTask, contactBucketMappingsTask); + + var employeeBucketMappings = employeeBucketMappingsTask.Result; + var contactBucketMappings = contactBucketMappingsTask.Result; + + var bucketVMs = new List(); + + // Prepare view models for each bucket + foreach (var bucket in bucketList) + { + var mappedEmployees = employeeBucketMappings + .Where(eb => eb.BucketId == bucket.Id) + .Select(eb => eb.EmployeeId) + .ToList(); + + if (bucket.CreatedBy != null) + { + mappedEmployees.Add(bucket.CreatedBy.Id); + } + + var contactCount = contactBucketMappings.Count(cb => cb.BucketId == bucket.Id); + + var bucketVM = bucket.ToAssignBucketVMFromBucket(); + bucketVM.EmployeeIds = mappedEmployees.Distinct().ToList(); + bucketVM.NumberOfContacts = contactCount; + + bucketVMs.Add(bucketVM); + } + + _logger.LogInfo("Fetched {BucketCount} buckets for Employee {EmployeeId} successfully", bucketVMs.Count, loggedInEmployee.Id); + return ApiResponse.SuccessResponse(bucketVMs, $"{bucketVMs.Count} buckets fetched successfully", 200); } - public async Task> CreateBucket(CreateBucketDto bucketDto) + public async Task> CreateBucketAsync(CreateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee) { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - if (bucketDto != null) + if (bucketDto == null) { - var assignedRoleIds = await _context.EmployeeRoleMappings.Where(r => r.EmployeeId == LoggedInEmployee.Id).Select(r => r.RoleId).ToListAsync(); - var permissionIds = await _context.RolePermissionMappings.Where(rp => assignedRoleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).Distinct().ToListAsync(); - var demo = !permissionIds.Contains(PermissionsMaster.DirectoryUser); - if (!permissionIds.Contains(PermissionsMaster.DirectoryAdmin) && !permissionIds.Contains(PermissionsMaster.DirectoryAdmin) && !permissionIds.Contains(PermissionsMaster.DirectoryUser)) + _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sent empty payload", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("User sent empty Payload", "User sent empty Payload", 400); + } + + try + { + // Check permissions for the logged-in employee in parallel + var permissionTask = CheckPermissionsAsync(loggedInEmployee.Id); + + // Check if a bucket with the same name already exists (case insensitive) + var existingBucketTask = _context.Buckets.FirstOrDefaultAsync(b => b.Name.ToLower() == bucketDto.Name.ToLower()); + + await Task.WhenAll(permissionTask, existingBucketTask); + + var (hasAdminPermission, hasManagerPermission, hasUserPermission) = permissionTask.Result; + var existingBucket = existingBucketTask.Result; + + // If the user does not have any of the required permissions, deny access + if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission) { - _logger.LogWarning("Employee {EmployeeId} attemped to create a bucket, but do not have permission", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); + _logger.LogWarning("Employee {EmployeeId} attempted to create bucket without permission", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 403); } - var existingBucket = await _context.Buckets.FirstOrDefaultAsync(b => b.Name == bucketDto.Name); + // If bucket with the same name exists, return conflict response if (existingBucket != null) { - _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to create an existing bucket.", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("Bucket already existed", "Bucket already existed", 409); + _logger.LogWarning("Employee {EmployeeId} attempted to create an existing bucket with name '{BucketName}'", loggedInEmployee.Id, bucketDto.Name); + return ApiResponse.ErrorResponse("Bucket already exists", "Bucket already exists", 409); } - Bucket bucket = new Bucket + + // Create new bucket entity + var newBucket = new Bucket { Name = bucketDto.Name, Description = bucketDto.Description, CreatedAt = DateTime.UtcNow, - CreatedByID = LoggedInEmployee.Id, + CreatedByID = loggedInEmployee.Id, TenantId = tenantId }; - _context.Buckets.Add(bucket); - EmployeeBucketMapping employeeBucket = new EmployeeBucketMapping + // Add bucket to context + _context.Buckets.Add(newBucket); + + // Create mapping between employee and bucket + var employeeBucketMapping = new EmployeeBucketMapping { - EmployeeId = LoggedInEmployee.Id, - BucketId = bucket.Id + EmployeeId = loggedInEmployee.Id, + BucketId = newBucket.Id }; - _context.EmployeeBucketMappings.Add(employeeBucket); - await _context.SaveChangesAsync(); - bucket = await _context.Buckets.Include(b => b.CreatedBy).FirstOrDefaultAsync(b => b.Id == bucket.Id) ?? new Bucket(); - BucketVM bucketVM = bucket.ToBucketVMFromBucket(); - _logger.LogInfo("Employee Id {LoggedInEmployeeId} creayted new bucket {BucketId}", LoggedInEmployee.Id, bucket.Id); - return ApiResponse.SuccessResponse(bucketVM, "Bucket Created SuccessFully", 200); - } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400); - } - public async Task> UpdateBucket(Guid id, UpdateBucketDto bucketDto) - { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - if (bucketDto != null && id == bucketDto.Id) - { - var assignedRoleIds = await _context.EmployeeRoleMappings.Where(r => r.EmployeeId == LoggedInEmployee.Id).Select(r => r.RoleId).ToListAsync(); - var permissionIds = await _context.RolePermissionMappings.Where(rp => assignedRoleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).Distinct().ToListAsync(); - var employeeBuckets = await _context.EmployeeBucketMappings.Where(eb => eb.BucketId == id).ToListAsync(); - var bucketIds = employeeBuckets.Where(eb => eb.EmployeeId == LoggedInEmployee.Id).Select(eb => eb.BucketId).ToList(); - Bucket? bucket = await _context.Buckets.Include(b => b.CreatedBy).FirstOrDefaultAsync(b => b.Id == bucketDto.Id && b.TenantId == tenantId); + // Add employee-bucket mapping to context + _context.EmployeeBucketMappings.Add(employeeBucketMapping); + // Save changes to DB + await _context.SaveChangesAsync(); + + // Load the newly created bucket including creator info for response + var createdBucket = await _context.Buckets + .Include(b => b.CreatedBy) + .ThenInclude(e => e!.JobRole) + .FirstOrDefaultAsync(b => b.Id == newBucket.Id); + + var bucketVM = _mapper.Map(createdBucket); + + _logger.LogInfo("Employee {EmployeeId} successfully created bucket {BucketId}", loggedInEmployee.Id, newBucket.Id); + + return ApiResponse.SuccessResponse(bucketVM, "Bucket created successfully", 200); + } + catch (Exception ex) + { + // Log unexpected exceptions + _logger.LogError(ex, "Error occurred while employee {EmployeeId} was creating a bucket", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Internal server error", "Internal server error", 500); + } + } + public async Task> UpdateBucketAsync(Guid id, UpdateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee) + { + if (bucketDto == null || id != bucketDto.Id) + { + _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sent invalid or empty payload for bucket update.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Invalid or empty payload", "Invalid or empty payload", 400); + } + + try + { + // Check user permissions in parallel + var permissionTask = CheckPermissionsAsync(loggedInEmployee.Id); + + // Use IDbContextFactory to create separate contexts for parallel DB calls + using var employeeBucketContext = _dbContextFactory.CreateDbContext(); + using var bucketContext = _dbContextFactory.CreateDbContext(); + using var contactBucketContext = _dbContextFactory.CreateDbContext(); + + // Load employee buckets where BucketId == id in parallel + var employeeBucketsTask = employeeBucketContext.EmployeeBucketMappings + .Where(eb => eb.EmployeeId == loggedInEmployee.Id) + .ToListAsync(); + + // Load the bucket with CreatedBy for given id and tenantId + var bucketTask = bucketContext.Buckets + .Include(b => b.CreatedBy) + .ThenInclude(e => e!.JobRole) + .FirstOrDefaultAsync(b => b.Id == bucketDto.Id && b.TenantId == tenantId); + + // Await all parallel tasks + await Task.WhenAll(permissionTask, employeeBucketsTask, bucketTask); + + var (hasAdminPermission, hasManagerPermission, hasUserPermission) = permissionTask.Result; + var employeeBuckets = employeeBucketsTask.Result; + var bucket = bucketTask.Result; + + // Validate bucket exists if (bucket == null) { - _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to update a bucket but not found in database.", LoggedInEmployee.Id); + _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to update bucket {BucketId}, but it was not found in database.", loggedInEmployee.Id, bucketDto.Id); return ApiResponse.ErrorResponse("Bucket not found", "Bucket not found", 404); } - Bucket? accessableBucket = null; - if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin)) + // Determine if employee has access to update the bucket + var bucketIdsForEmployee = employeeBuckets.Select(eb => eb.BucketId).ToList(); + + bool hasAccess = false; + if (hasAdminPermission) { - accessableBucket = bucket; + hasAccess = true; } - else if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin) && bucketIds.Contains(id)) + else if (hasManagerPermission && bucketIdsForEmployee.Contains(id)) { - accessableBucket = bucket; + hasAccess = true; } - else if (permissionIds.Contains(PermissionsMaster.DirectoryUser)) + else if (hasUserPermission && bucket.CreatedByID == loggedInEmployee.Id) { - if (bucket.CreatedByID == LoggedInEmployee.Id) - { - accessableBucket = bucket; - } - } - if (accessableBucket == null) - { - _logger.LogWarning("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); - return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 401); + hasAccess = true; } - bucket.Name = bucketDto.Name ?? ""; - bucket.Description = bucketDto.Description ?? ""; + if (!hasAccess) + { + _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to update bucket {BucketId} without sufficient permissions.", loggedInEmployee.Id, bucket.Id); + return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 403); + } + // Update bucket properties safely + bucket.Name = bucketDto.Name ?? string.Empty; + bucket.Description = bucketDto.Description ?? string.Empty; + + // Log the update attempt in directory update logs _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog { RefereanceId = bucketDto.Id, - UpdatedById = LoggedInEmployee.Id, + UpdatedById = loggedInEmployee.Id, UpdateAt = DateTime.UtcNow }); + // Save changes to bucket and logs await _context.SaveChangesAsync(); - AssignBucketVM bucketVM = bucket.ToAssignBucketVMFromBucket(); - List employeeBucketMappings = employeeBuckets.Where(eb => eb.BucketId == bucket.Id).ToList(); - List contactBuckets = await _context.ContactBucketMappings.Where(eb => eb.BucketId == bucket.Id).ToListAsync(); - var employeeIds = employeeBucketMappings.Select(eb => eb.EmployeeId).ToList(); - bucketVM.EmployeeIds = employeeIds; + // Now load contacts related to the bucket using a separate context for parallelism + var contactBuckets = await contactBucketContext.ContactBucketMappings + .Where(cb => cb.BucketId == bucket.Id) + .ToListAsync(); + + // Prepare view model to return + AssignBucketVM bucketVM = _mapper.Map(bucket); + bucketVM.EmployeeIds = employeeBuckets.Where(eb => eb.BucketId == bucket.Id).Select(eb => eb.EmployeeId).ToList(); bucketVM.NumberOfContacts = contactBuckets.Count; - _logger.LogInfo("Employee Id {LoggedInEmployeeId} Updated new bucket {BucketId}", LoggedInEmployee.Id, bucket.Id); - return ApiResponse.SuccessResponse(bucketVM, "Bucket update successFully", 200); + _logger.LogInfo("Employee ID {LoggedInEmployeeId} successfully updated bucket ID {BucketId}.", loggedInEmployee.Id, bucket.Id); + + return ApiResponse.SuccessResponse(bucketVM, "Bucket updated successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while employee ID {LoggedInEmployeeId} attempted to update bucket ID {BucketId}.", loggedInEmployee.Id, id); + return ApiResponse.ErrorResponse("An unexpected error occurred. Please try again later.", "Internal server error", 500); } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400); } + public async Task> AssignBucket(Guid bucketId, List assignBuckets) { Guid tenantId = _userHelper.GetTenantId(); @@ -1713,7 +1818,7 @@ namespace Marco.Pms.Services.Service if (accessableBucket == null) { _logger.LogWarning("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); - return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 401); + return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 403); } var employeeIds = await _context.Employees.Where(e => e.TenantId == tenantId && e.IsActive).Select(e => e.Id).ToListAsync(); int assignedEmployee = 0; @@ -1814,7 +1919,7 @@ namespace Marco.Pms.Services.Service if (accessableBucket == null) { _logger.LogWarning("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); - return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 401); + return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 403); } _context.EmployeeBucketMappings.RemoveRange(employeeBuckets); diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs index 1204ce7..ee12e23 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs @@ -8,20 +8,24 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces { Task> GetListOfContactsAsync(string? search, string? filter, Guid? projectId, bool active, int pageSize, int pageNumber, Guid tenantId, Employee loggedInEmployee); Task> GetListOfContactsOld(string? search, bool active, ContactFilterDto? filterDto, Guid? projectId); - Task> GetContactsListByBucketId(Guid id); + Task> GetContactsListByBucketIdAsync(Guid bucketId, Guid tenantId, Employee loggedInEmployee); Task> GetContactProfileAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task> GetOrganizationListAsync(Guid tenantId, Employee loggedInEmployee); Task> CreateContactAsync(CreateContactDto createContact, Guid tenantId, Employee loggedInEmployee); - Task> UpdateContact(Guid id, UpdateContactDto updateContact); - Task> DeleteContact(Guid id, bool active); + Task> UpdateContactAsync(Guid id, UpdateContactDto updateContact, Guid tenantId, Employee loggedInEmployee); + Task> DeleteContactAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee); + + Task> GetListOFAllNotes(Guid? projectId, int pageSize, int pageNumber); Task> GetNoteListByContactId(Guid id, bool active); Task> CreateContactNote(CreateContactNoteDto noteDto); Task> UpdateContactNote(Guid id, UpdateContactNoteDto noteDto); Task> DeleteContactNote(Guid id, bool active); - Task> GetBucketList(); - Task> CreateBucket(CreateBucketDto bucketDto); - Task> UpdateBucket(Guid id, UpdateBucketDto bucketDto); + + + Task> GetBucketListAsync(Guid tenantId, Employee loggedInEmployee); + Task> CreateBucketAsync(CreateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee); + Task> UpdateBucketAsync(Guid id, UpdateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee); Task> AssignBucket(Guid bucketId, List assignBuckets); Task> DeleteBucket(Guid id); }