This commit is contained in:
Zeeshaun
2026-03-31 16:43:12 -05:00
parent 9be174da46
commit d639027028
198 changed files with 111474 additions and 0 deletions
@@ -0,0 +1,13 @@
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class ApiConfiguration
{
public string AppName { get; set; }
public string SeqUrl { get; set; }
public string SeqKey { get; set; }
public string[] CorsAllowedOrigins { get; set; }
public FeatureFlags FeatureFlags { get; set; }
public FeatureGates FeatureGates { get; set; }
public string XdiBatchProcessCronSchedule { get; set; }
public string DnaDataAuditProcessCronSchedule { get; set; }
}
@@ -0,0 +1,16 @@
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class CoreApiWrapperConfiguration
{
public string CoreApiBaseUrl { get; set; }
public string CoreApiGetAgreementByUserId { get; set; }
public string CoreApiGetAgreementByAgreementId { get; set; }
public string CoreApiGetAgreementsByAccountId { get; set; }
public string CoreApiGetAgreementAccountByAccountId { get; set; }
public string CoreApiGetAgreementAccountsByUserId { get; set; }
public string CoreApiGetAgreementAccountsByOrganizationId { get; set; }
public string CoreApiGetAuditAccountsFromDateLastMaintained { get; set; }
public string CoreApiGetAuditAgreementFromDateLastMaintained { get; set; }
public string CoreApiGetAuditOrganizationFromDateLastMaintained { get; set; }
public string CoreApiUpdateAccountsMaintenanceRequest { get; set; }
}
@@ -0,0 +1,7 @@
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class DnaAuditProcessConfiguration
{
public int LookBackMinutes { get; set; }
public int NumberOfAllowedConsecutiveFailures { get; set; }
}
@@ -0,0 +1,11 @@
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class FeatureFlags
{
public bool RunHostedServices { get; set; }
public bool RunXdiBatchProcessConsumer { get; set; }
public bool RunXdiSshResponseConsumer { get; set; }
public bool RunAccountDnaFieldChangeConsumer { get; set; }
public bool RunAgreementDnaFieldChangeConsumer { get; set; }
public bool RunOrganizationDnaFieldChangeConsumer { get; set; }
}
@@ -0,0 +1,7 @@
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class FeatureGates
{
public const string EnableControllers = "EnableControllers";
public const string BounceBackNotifications = "BounceBackNotifications";
}
@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class FtpApiConfiguration
{
public string Url { get; set; }
public string QueueSshFtpRequestEndpoint { get; set; }
[NotMapped]
public string QueueSshFtpRequestEndpointUrl => Url + QueueSshFtpRequestEndpoint;
}
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class NotificationApiConfiguration
{
public string Url { get; set; }
public string Email { get; set; }
public int RetryCount { get; set; }
[NotMapped]
public string EmailUrl => Url + Email;
public List<string> DNACoreErrorEmailList { get; set; }
public List<string> TestEmailList { get; set; }
public bool ShouldSendRealEmails { get; set; }
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.LcuMassTransitModels;
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class ReceiveEndpointConfigurations
{
public ReceiveEndpointConfiguration XdiBatchProcessEndpoint { get; set; }
public ReceiveEndpointConfiguration XdiBatchConfirmEndpoint { get; set; }
public ReceiveEndpointConfiguration DnaAccountAuditEndpoint { get; set; }
public ReceiveEndpointConfiguration DnaOrganizationAuditEndpoint { get; set; }
public ReceiveEndpointConfiguration DnaAgreementAuditEndpoint { get; set; }
}
@@ -0,0 +1,8 @@
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class SqlConnectionConfiguration
{
public string ConnectionString { get; set; }
public int MaxRetryCount { get; set; }
public TimeSpan MaxRetryDelay { get; set; }
}
@@ -0,0 +1,11 @@
namespace LCU.API.Internal.MemberPreferencesApi.Configuration;
public class XdiSftpApiConfiguration
{
public string HostName { get; set; }
public int PortNumber { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string Fingerprint { get; set; }
public string SftpRootBasePath { get; set; }
}
@@ -0,0 +1,43 @@
using LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Consumers;
public class AccountDnaFieldChangeConsumer : IConsumer<DnaFieldChangeRequest>
{
private readonly IServiceScopeFactory _scopeFactory;
public AccountDnaFieldChangeConsumer(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task Consume(ConsumeContext<DnaFieldChangeRequest> context)
{
try
{
Log.Debug("Starting AccountDnaFieldChangeConsumer.");
Log.Information($"New Account Received for: {context.Message.DnaProcessType} Process - {context.Message.DnaFieldIdInformation}");
using var scope = _scopeFactory.CreateScope();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDNADataAuditService>().HandleAccountAuditCheck(context.Message));
Log.Debug("Ending DnaAccountAuditServiceConsumer.");
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"AccountDnaFieldChangeConsumer Failure. " +
$"The request is placed on AccountDnaFieldChangeConsumer_error.");
throw serviceException;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Unexpected AccountDnaFieldChangeConsumer Failure. " +
$"The request is placed on AccountDnaFieldChangeConsumer_error.");
throw;
}
}
}
@@ -0,0 +1,42 @@
using LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Consumers;
public class AgreementDnaFieldChangeConsumer : IConsumer<DnaFieldChangeRequest>
{
private readonly IServiceScopeFactory _scopeFactory;
public AgreementDnaFieldChangeConsumer(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task Consume(ConsumeContext<DnaFieldChangeRequest> context)
{
try
{
Log.Debug("Starting AgreementDnaFieldChangeConsumer.");
Log.Information($"New Agreement Received for : {context.Message.DnaProcessType} Process - {context.Message.DnaFieldIdInformation}");
using var scope = _scopeFactory.CreateScope();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDNADataAuditService>().HandleAgreementAuditCheck(context.Message));
Log.Debug("Ending AgreementDnaFieldChangeConsumer.");
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"AgreementDnaFieldChangeConsumer Failure. " +
$"The request is placed on AgreementDnaFieldChangeConsumer_error.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Unexpected AgreementDnaFieldChangeConsumer Failure. " +
$"The TransactionRequest is placed on AgreementDnaFieldChangeConsumer_error.");
throw;
}
}
}
@@ -0,0 +1,43 @@
using LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Consumers;
public class OrganizationDnaFieldChangeConsumer : IConsumer<DnaFieldChangeRequest>
{
private readonly IServiceScopeFactory _scopeFactory;
public OrganizationDnaFieldChangeConsumer(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task Consume(ConsumeContext<DnaFieldChangeRequest> context)
{
try
{
Log.Debug("Starting OrganizationDnaFieldChangeConsumer.");
Log.Information($"New Organization Received for: {context.Message.DnaProcessType} Process - {context.Message.DnaFieldIdInformation}");
using var scope = _scopeFactory.CreateScope();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDNADataAuditService>().HandleOrganizationAuditCheck(context.Message));
Log.Debug("Ending OrganizationDnaFieldChangeConsumer.");
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"OrganizationDnaFieldChangeConsumer Failure. " +
$"The request is placed on OrganizationDnaFieldChangeConsumer_error.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Unexpected OrganizationDnaFieldChangeConsumer Consumer Failure. " +
$"The request is placed on OrganizationDnaFieldChangeConsumer_error.");
throw;
}
}
}
@@ -0,0 +1,38 @@
using LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Consumers;
public class XdiBatchProcessConsumer : IConsumer<XdiBatchRequest>
{
private readonly IServiceScopeFactory _scopeFactory;
public XdiBatchProcessConsumer(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task Consume(ConsumeContext<XdiBatchRequest> context)
{
try
{
using var scope = _scopeFactory.CreateScope();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IXdiBatchService>().CreateXdiBatchFiles(context.Message));
}
catch (ServiceException ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error($"XdiBatchProcessConsumer Failure. " +
$"The response is placed on LCU.MemberPreferencesApi.XdiAuditProcess_error. " +
$"Error Message: {ex.Message}");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Unexpected XdiBatchProcessConsumer Failure. " +
$"The response is placed on LCU.MemberPreferencesApi.XdiAuditProcess_error.");
throw;
}
}
}
@@ -0,0 +1,39 @@
using ApiResponse = LCU.Library.Internal.ApiResponse.Models;
using LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Consumers;
public class XdiSshResponseConsumer : IConsumer<ApiResponse.Response>
{
private readonly IServiceScopeFactory _scopeFactory;
public XdiSshResponseConsumer(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task Consume(ConsumeContext<ApiResponse.Response> context)
{
try
{
using var scope = _scopeFactory.CreateScope();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IXdiBatchService>().MarkPendingSuccessful(context.Message));
}
catch (ServiceException ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error($"XdiSshResponseConsumer Failure. " +
$"The ApiResponse.Response is placed on LCU.MemberPreferencesApi.XdiBatchConfirm_error. " +
$"Error Message: {ex.Message}");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Unexpected XdiSshResponseConsumer Failure. " +
$"The ApiResponse.Response is placed on LCU.MemberPreferencesApi.XdiBatchConfirm_error.");
throw;
}
}
}
@@ -0,0 +1,79 @@
using LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
using Microsoft.AspNetCore.Mvc;
namespace LCU.API.Internal.MemberPreferencesApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AuditController : ControllerBase
{
private readonly IQueueDnaAccountAuditService _dnaAccountAuditService;
private readonly IQueueDnaOrganizationAuditService _dnaOrganizationAuditService;
private readonly IQueueDnaAgreementAuditService _dnaAgreementAuditService;
public AuditController(IQueueDnaAccountAuditService dnaAccountAuditService,
IQueueDnaOrganizationAuditService dnaOrganizationAuditService,
IQueueDnaAgreementAuditService dnaAgreementAuditService)
{
_dnaAccountAuditService = dnaAccountAuditService;
_dnaOrganizationAuditService = dnaOrganizationAuditService;
_dnaAgreementAuditService = dnaAgreementAuditService;
}
[HttpPost("EnqueueDNAOrganizationAuditRecords")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<IActionResult> EnqueueDNAOrganizationAuditRecords([FromBody] List<long> organizationIds)
{
try
{
Log.Information($"Forced {organizationIds.Count} DNAOrganizationAuditRecords to be queued");
await _dnaOrganizationAuditService.QueueDataAuditForOrganizations(organizationIds);
return Ok();
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}");
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
}
}
[HttpPost("EnqueueDNAAgreementAuditRecords")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<IActionResult> EnqueueDNAAgreementAuditRecords([FromBody] List<long> agreementIds)
{
try
{
Log.Information($"Forced {agreementIds.Count} DNAAgreementAuditRecords to be queued");
await _dnaAgreementAuditService.QueueDataAuditForAgreements(agreementIds);
return Ok();
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}");
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
}
}
[HttpPost("EnqueueDNAAccountAuditRecords")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<IActionResult> EnqueueDNAAccountAuditRecords([FromBody] List<long> accountNumbers)
{
try
{
Log.Information($"Forced {accountNumbers.Count} DNAAccountAuditRecords to be queued");
await _dnaAccountAuditService.QueueDataAuditForAccounts(accountNumbers);
return Ok();
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}");
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
}
}
}
@@ -0,0 +1,55 @@
using LCU.API.Internal.MemberPreferencesApi.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement;
namespace LCU.API.Internal.MemberPreferencesApi.Controllers
{
[Route("api/{userId}")]
[ApiController]
public class MemberEmailController : ControllerBase
{
private readonly IMemberEmailService _emailService;
private readonly IFeatureManager _featureManager;
public MemberEmailController(IMemberEmailService emailService,
IFeatureManager featureManager)
{
_emailService = emailService;
_featureManager = featureManager;
}
[HttpPost("SuccessfulSend")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[ReadsCurrentUser]
public async Task<IActionResult> EmailSentSuccessfully(string userId, [FromQuery] string email)
{
var emailUpdated = await _emailService.EmailSucceededToSend(userId, email);
if (!emailUpdated)
{
return NotFound($"unknown email {email} for {userId}");
}
return Ok();
}
[HttpPost("FailedSend")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[ReadsCurrentUser]
public async Task<IActionResult> EmailSendFailed(string userId, [FromQuery] string email)
{
var enablePolicy = await _featureManager.IsEnabledAsync(FeatureGates.BounceBackNotifications);
var emailUpdated = await _emailService.EmailFailedToSend(userId, email, enablePolicy);
if (!emailUpdated)
{
return NotFound($"unknown email {email} for {userId}");
}
return Ok();
}
}
}
@@ -0,0 +1,136 @@
using LCU.API.Internal.MemberPreferencesApi.Services;
using LCU.Library.Internal.MemberPreferencesApiModels.DataTransfer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
namespace LCU.API.Internal.MemberPreferencesApi.Controllers;
[FeatureGate(FeatureGates.EnableControllers)]
[ApiController]
[Route("api/[controller]")]
public class MemberOptInSettingsController : ControllerBase
{
private readonly IOptinService _unifiedOptinService;
private readonly IMemberPreferenceService _memberPreferenceService;
public MemberOptInSettingsController(IOptinService unifiedOptinService,
IMemberPreferenceService memberPreferenceService)
{
_unifiedOptinService = unifiedOptinService;
_memberPreferenceService = memberPreferenceService;
}
[HttpGet("AgreementInformationForAccount/{accountId:long}")]
[ProducesResponseType(typeof(IEnumerable<MemberOptInAgreementInformation>), 200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAgreementsByAccountId(long accountId)
{
try
{
if (accountId == 0)
{
return BadRequest("An account id must be provided to search by, and cannot be 0.");
}
var result = await _unifiedOptinService.GetMemberOptInAgreementInformationByAccountId(accountId);
if (result != null && result.Any())
{
return Ok(result);
}
return NotFound($"No Opt-In Agreement Settings found for accountId:{accountId}");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpGet("AgreementInformation/{userId}")]
[ProducesResponseType(typeof(MemberOptInAgreementInformation), 200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAgreementByUserId(string userId)
{
try
{
if (string.IsNullOrEmpty(userId))
{
return BadRequest("An UserId id must be provided to search by.");
}
var result = await _unifiedOptinService.GetMemberOptInAgreementInformationForUser(userId);
if (result != null)
{
return Ok(result);
}
return NotFound($"No Opt-In Agreement Settings found for userId:{userId}");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpGet("{userId}")]
[ProducesResponseType(typeof(MemberOptInInformation), 200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetMemberById(string userId)
{
try
{
if (string.IsNullOrEmpty(userId))
{
return BadRequest("An user id must be provided to search by and cannot be 0.");
}
var result = await _unifiedOptinService.GetMemberOptInSettingsOrCreateDefaultSetting(userId);
if (result != null)
{
var memberOptinSettings = await _unifiedOptinService.TransformMemberOptInSettingsForClient(result);
return Ok(memberOptinSettings);
}
return NotFound($"Requested Opt-In Settings for {userId} were not found.");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
public async Task<IActionResult> UpdateOptinPreference(MemberOptinRequest memberPreference,
[FromHeader] string userName, [FromHeader] string userType)
{
try
{
if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(userType))
{
return BadRequest("Missing User Headers");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await _memberPreferenceService.UpdateMemberOptinAsync(memberPreference);
return Ok();
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
@@ -0,0 +1,65 @@
using LCU.API.Internal.MemberPreferencesApi.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
namespace LCU.API.Internal.MemberPreferencesApi.Controllers;
[FeatureGate(FeatureGates.EnableControllers)]
[ApiController]
[Route("api/[controller]")]
public class MemberPreferenceController : ControllerBase
{
private readonly IMemberPreferenceService _memberPreferenceService;
public MemberPreferenceController(IMemberPreferenceService memberPreferenceService)
{
_memberPreferenceService = memberPreferenceService;
}
[HttpGet("AvailableTypes")]
public IActionResult GetAvailableTypes()
{
return Ok(_memberPreferenceService.GetAvailableTypes());
}
[HttpGet("{userId}")]
public async Task<IActionResult> Get(string userId)
{
try
{
var result = await _memberPreferenceService.GetMemberPreferencesAsync(userId);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
public async Task<IActionResult> CreateMemberPreference(MemberPreferenceRequest memberPreference,
[FromHeader] string userName, [FromHeader] string userType)
{
try
{
if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(userType))
{
return BadRequest("Missing User Headers");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _memberPreferenceService.SaveMemberPreferenceAsync(memberPreference);
return CreatedAtAction(nameof(CreateMemberPreference), new {userId = memberPreference.UserId}, memberPreference);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name}.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
@@ -0,0 +1,34 @@
using LCU.Library.Internal.Status.Services;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class StatusController : ControllerBase
{
private readonly IApiHealthCheckService _apiHealthCheckService;
public StatusController(IApiHealthCheckService apiHealthCheckService)
{
_apiHealthCheckService = apiHealthCheckService;
}
[HttpGet]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public IActionResult Get()
{
try
{
return Ok(_apiHealthCheckService.CheckApiHealthAndBuildEnvironmentView());
}
catch (ServiceException exception)
{
return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
}
catch (Exception exception)
{
Log.Error(exception, "Unexpected Exception");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
@@ -0,0 +1,114 @@
using LCU.Library.Internal.ApiSecurity.Enums;
using LCU.Library.Internal.ApiSecurity.Models;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Agreements;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.CoreRequests;
using LCU.Library.Internal.LcuHttpClient.HttpRequest;
using LCU.Library.Internal.LcuHttpClient.Utilities;
namespace LCU.API.Internal.MemberPreferencesApi.DataAccess;
public class CoreApiWrapperDao : ICoreApiWrapperDao
{
private readonly ApiSecurityConfiguration _apiSecurityConfiguration;
private readonly CoreApiWrapperConfiguration _coreApiWrapperConfig;
private readonly IGetRequest _getRequest;
private readonly IPutRequest _putRequest;
private static readonly List<string> AccountTypesToFilterOut = new List<string> { "EXT", "BKCK", "RTMT", "RR", "LEAS" };
private const string ClientKeyValue = "MembershipPreferences";
public CoreApiWrapperDao(ApiSecurityConfiguration apiSecurityConfiguration, CoreApiWrapperConfiguration coreApiWrapperConfig,
IGetRequest getRequest, IPutRequest putRequest)
{
_apiSecurityConfiguration = apiSecurityConfiguration;
_coreApiWrapperConfig = coreApiWrapperConfig;
_getRequest = getRequest;
_putRequest = putRequest;
}
public async Task<Agreement> GetAgreementByUserId(string userId)
{
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAgreementByUserId.Replace("{userId}", userId)}";
return await _getRequest.Async<Agreement>(path, 0);
}
public async Task<Agreement> GetAgreementByAgreementId(long agreementId)
{
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAgreementByAgreementId.Replace("{agreementId}", agreementId.ToString())}";
return await _getRequest.Async<Agreement>(path, 0);
}
public async Task<IEnumerable<Agreement>> GetAgreementsByAccountId(long accountId)
{
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAgreementsByAccountId.Replace("{accountId}", accountId.ToString())}";
return await _getRequest.Async<IEnumerable<Agreement>>(path, 0);
}
public async Task<AgreementAccount> GetAgreementAccountByAccountId(long accountId)
{
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAgreementAccountByAccountId.Replace("{accountId}", accountId.ToString())}";
var response = await _getRequest.Async<AgreementAccount>(path, 0);
if (response == null)
{
return null;
}
return AccountTypesToFilterOut.Contains(response.MajorAccountTypeCode) ? null : response;
}
public async Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByUserId(string userId)
{
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAgreementAccountsByUserId.Replace("{userId}", userId)}";
var response = await _getRequest.Async<IEnumerable<AgreementAccount>>(path, 0);
return response.Where(account => !AccountTypesToFilterOut.Contains(account.MajorAccountTypeCode));
}
public async Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByOrganizationId(long organizationId)
{
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAgreementAccountsByOrganizationId.Replace("{organizationId}", organizationId.ToString())}";
var response = await _getRequest.Async<IEnumerable<AgreementAccount>>(path, 0);
return response.Where(account => !AccountTypesToFilterOut.Contains(account.MajorAccountTypeCode));
}
public async Task<IEnumerable<long>> GetAuditAccountsFromDateLastMaintained(DateTime dateTime)
{
var dtString = dateTime.ToString("s").Replace(":", "%3A");
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAuditAccountsFromDateLastMaintained}?dateLastMaintained={dtString}";
return await _getRequest.Async<IEnumerable<long>>(path, 0, 240);
}
public async Task<IEnumerable<long>> GetAuditAgreementFromDateLastMaintained(DateTime dateTime)
{
var dtString = dateTime.ToString("s").Replace(":", "%3A");
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAuditAgreementFromDateLastMaintained}?dateLastMaintained={dtString}";
return await _getRequest.Async<IEnumerable<long>>(path, 0, 240);
}
public async Task<IEnumerable<long>> GetAuditOrganizationFromDateLastMaintained(DateTime dateTime)
{
var dtString = dateTime.ToString("s").Replace(":", "%3A");
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiGetAuditOrganizationFromDateLastMaintained}?dateLastMaintained={dtString}";
return await _getRequest.Async<IEnumerable<long>>(path, 0, 240);
}
public async Task UpdateAgreementAccountsDeliveryMethod(IEnumerable<long> accountIds, string deliveryMethod, string userId)
{
var path = $"{_coreApiWrapperConfig.CoreApiBaseUrl}{_coreApiWrapperConfig.CoreApiUpdateAccountsMaintenanceRequest}";
var uri = new Uri(path);
var accountMaintenanceRequests = new List<AccountMaintenanceRequest>();
foreach (var accountId in accountIds)
{
var accountMaintenanceRequest = new AccountMaintenanceRequest
{
AccountNumber = accountId,
DeliveryMethod = deliveryMethod
};
accountMaintenanceRequests.Add(accountMaintenanceRequest);
}
await _putRequest.Async(uri, accountMaintenanceRequests, HttpUtilities.GetApiKeyHeaders(ClientKeyValue,
_apiSecurityConfiguration.ExtractApiKey(ClientKeyValue, ApiKeySetName.InternalKeySet)), 0);
}
}
@@ -0,0 +1,21 @@
using LCU.Api.Internal.FTPLib.Models;
using LCU.Library.Internal.LcuHttpClient.HttpRequest;
namespace LCU.API.Internal.MemberPreferencesApi.DataAccess;
public class FtpApiDao : IFtpApiDao
{
private readonly FtpApiConfiguration _ftpApiConfiguration;
private readonly IPostRequest _postRequest;
public FtpApiDao(FtpApiConfiguration ftpApiConfiguration, IPostRequest postRequest)
{
_ftpApiConfiguration = ftpApiConfiguration;
_postRequest = postRequest;
}
public async Task SendFtpAsync(SshFtpRequest request)
{
await _postRequest.Async(new Uri(_ftpApiConfiguration.QueueSshFtpRequestEndpointUrl), request, 0);
}
}
@@ -0,0 +1,17 @@
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Agreements;
namespace LCU.API.Internal.MemberPreferencesApi.DataAccess;
public interface ICoreApiWrapperDao
{
public Task<Agreement> GetAgreementByUserId(string userId);
public Task<Agreement> GetAgreementByAgreementId(long agreementId);
public Task<IEnumerable<Agreement>> GetAgreementsByAccountId(long accountId);
public Task<AgreementAccount> GetAgreementAccountByAccountId(long accountId);
public Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByUserId(string userId);
public Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByOrganizationId(long organizationId);
public Task<IEnumerable<long>> GetAuditAccountsFromDateLastMaintained(DateTime dateTime);
public Task<IEnumerable<long>> GetAuditAgreementFromDateLastMaintained(DateTime dateTime);
public Task<IEnumerable<long>> GetAuditOrganizationFromDateLastMaintained(DateTime dateTime);
public Task UpdateAgreementAccountsDeliveryMethod(IEnumerable<long> accountIds, string deliveryMethod, string userId);
}
@@ -0,0 +1,8 @@
using LCU.Api.Internal.FTPLib.Models;
namespace LCU.API.Internal.MemberPreferencesApi.DataAccess;
public interface IFtpApiDao
{
public Task SendFtpAsync(SshFtpRequest sftpRequest);
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.DataAccess;
public interface INotificationApiDao
{
public Task SendEmailAsync(EmailRequest emailRequest);
}
@@ -0,0 +1,20 @@
using LCU.Library.Internal.LcuHttpClient.HttpRequest;
namespace LCU.API.Internal.MemberPreferencesApi.DataAccess;
public class NotificationApiDao : INotificationApiDao
{
private readonly NotificationApiConfiguration _notificationApiConfiguration;
private readonly IPostRequest _postRequest;
public NotificationApiDao(NotificationApiConfiguration notificationApiConfiguration, IPostRequest postRequest)
{
_notificationApiConfiguration = notificationApiConfiguration;
_postRequest = postRequest;
}
public async Task SendEmailAsync(EmailRequest emailRequest)
{
await _postRequest.Async(new Uri(_notificationApiConfiguration.EmailUrl), emailRequest, _notificationApiConfiguration.RetryCount);
}
}
@@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LCU.Api.Internal.FTPLib" Version="2.11.2" />
<PackageReference Include="LCU.Library.Internal.ApiSecurity" Version="1.0.2" />
<PackageReference Include="LCU.Library.Internal.Configuration" Version="1.0.0" />
<PackageReference Include="LCU.Library.Internal.CoreApiWrapperModel" Version="2.27.14" />
<PackageReference Include="LCU.Api.Internal.MaidBotLib" Version="1.0.2" />
<PackageReference Include="LCU.Library.Internal.Exceptions" Version="1.1.0" />
<PackageReference Include="LCU.Library.Internal.LcuHttpClient" Version="1.21.0" />
<PackageReference Include="LCU.Library.Internal.LcuMassTransit" Version="1.0.0" />
<PackageReference Include="LCU.Library.Internal.LcuMassTransitModels" Version="1.1.0" />
<PackageReference Include="LCU.Library.Internal.Logging" Version="1.1.0" />
<PackageReference Include="LCU.Library.Internal.Status" Version="1.1.0" />
<PackageReference Include="LCU.Library.Internal.StatusModels" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.FeatureManagement" Version="2.6.0" />
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.6.0" />
<PackageReference Include="NCrontab.Signed" Version="3.3.3" />
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="TimeZoneConverter" Version="3.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LCU.API.Internal.MemberPreferencesApiLib\LCU.API.Internal.MemberPreferencesApiLib.csproj" />
<ProjectReference Include="..\LCU.Library.Internal.MemberPreferencesApiModels\LCU.Library.Internal.MemberPreferencesApiModels.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Properties\launchSettings.json">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,23 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using LCU.Library.Internal.MemberPreferencesApiModels.Models;
namespace LCU.API.Internal.MemberPreferencesApi.Models.EmailPolicies;
public class EStatementsSixtyDaysUndeliverableEmailPolicy : SixtyDaysUndeliverableEmailPolicy
{
protected override EmailStatus? Status => UserOptIn.EStatementsEmailStatus;
protected override DateTime FirstUndeliverable => UserOptIn.EStatementsEmailFirstSent!.Value;
protected override DateTime LastUndeliverable => UserOptIn.EStatementsEmailLastSent!.Value;
protected override PreferenceType PreferenceType => PreferenceType.EStatements;
protected override bool EligibleForPreferenceFlip => !UserOptIn.HasAnyUnifiedPreference() && UserOptIn.EStatements == "Electronic";
protected override string Preference
{
set => UserOptIn.UniversalOptIn = value;
}
public EStatementsSixtyDaysUndeliverableEmailPolicy(IAuditableChanges auditableTracker, IMemberPreferences memberPreferences)
: base(auditableTracker, memberPreferences)
{
}
}
@@ -0,0 +1,9 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using System.Threading.Tasks;
namespace LCU.API.Internal.MemberPreferencesApi.Models.EmailPolicies;
public interface IEmailFailurePolicy
{
public Task<bool> Apply(MemberOptInSettings userOptIn, bool enablePolicy);
}
@@ -0,0 +1,74 @@
using System;
using System.Threading.Tasks;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using LCU.Library.Internal.MemberPreferencesApiModels.Models;
using Serilog;
namespace LCU.API.Internal.MemberPreferencesApi.Models.EmailPolicies;
public abstract class SixtyDaysUndeliverableEmailPolicy : IEmailFailurePolicy
{
private const string UpdatedByUser = "BounceBackLimitReached";
private readonly IAuditableChanges _auditableTracker;
private readonly IMemberPreferences _memberPreferences;
protected MemberOptInSettings UserOptIn { get; private set; }
protected abstract EmailStatus? Status { get; }
protected abstract DateTime FirstUndeliverable { get; }
protected abstract DateTime LastUndeliverable { get; }
protected abstract string Preference { set; }
protected abstract PreferenceType PreferenceType { get; }
protected abstract bool EligibleForPreferenceFlip { get; }
protected SixtyDaysUndeliverableEmailPolicy(IAuditableChanges auditableTracker, IMemberPreferences memberPreferences)
{
_auditableTracker = auditableTracker;
_memberPreferences = memberPreferences;
}
public async Task<bool> Apply(MemberOptInSettings userOptIn, bool enablePolicy)
{
if (!enablePolicy)
{
return false;
}
UserOptIn = userOptIn;
if (Status != EmailStatus.FailingDelivery)
{
return false;
}
if ((LastUndeliverable - FirstUndeliverable).Days < 60)
{
return false;
}
if (!EligibleForPreferenceFlip)
{
return false;
}
var preferenceChange = new MemberPreference
{
Preference = PreferenceType.UnifiedOptIn,
UserId = userOptIn.UserId,
LastUpdatedBy = userOptIn.LastUpdatedBy = UpdatedByUser,
Value = Preference = DeliveryPreference.Paper,
Created = DateTime.UtcNow,
CreatedByType = UserType.System,
LastUpdated = DateTime.UtcNow,
LegalReference = UpdatedByUser
};
await _memberPreferences.Add(preferenceChange);
Log.Information($"UserId: {userOptIn.UserId} has reached BounceBack status from failed delivery of {PreferenceType} email: UniversalOptIn set to Paper.");
_auditableTracker.SystemModified(preferenceChange, preferenceChange.LastUpdatedBy);
_auditableTracker.SystemModified(userOptIn, preferenceChange.LastUpdatedBy);
return true;
}
}
@@ -0,0 +1,25 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using LCU.Library.Internal.MemberPreferencesApiModels.Models;
using System;
namespace LCU.API.Internal.MemberPreferencesApi.Models.EmailPolicies;
public class UnifiedSixtyDaysUndeliverableEmailPolicy : SixtyDaysUndeliverableEmailPolicy
{
protected override EmailStatus? Status => UserOptIn.UnifiedOptInEmailStatus;
protected override DateTime FirstUndeliverable => UserOptIn.UnifiedOptInEmailFirstSent!.Value;
protected override DateTime LastUndeliverable => UserOptIn.UnifiedOptInEmailLastSent!.Value;
protected override PreferenceType PreferenceType => PreferenceType.UnifiedOptIn;
protected override bool EligibleForPreferenceFlip => UserOptIn.UniversalOptIn == "Electronic";
protected override string Preference
{
set => UserOptIn.UniversalOptIn = value;
}
public UnifiedSixtyDaysUndeliverableEmailPolicy(IAuditableChanges auditableTracker, IMemberPreferences memberPreferences)
: base(auditableTracker, memberPreferences)
{
}
}
@@ -0,0 +1,7 @@
namespace LCU.API.Internal.MemberPreferencesApi.Models;
public class FtpQueuePayloadItem
{
public string UserId { get; set; }
public string Value { get; set; }
}
@@ -0,0 +1,14 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using System.Text.Json.Serialization;
namespace LCU.API.Internal.MemberPreferencesApi.Models.Requests;
public class DnaFieldChangeRequest
{
public long DnaFieldIdInformation { get; set; }
public int ProcessId { get; set; }
[System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))]
public DnaAuditProcessType DnaProcessType { get; set; }
public DateTime LastUpdated { get; set; }
public int ConsecutiveFailures { get; set; }
}
@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace LCU.API.Internal.MemberPreferencesApi.Models.Requests;
public class EmailRequest
{
[Required] public List<string> Recipient { get; set; }
[Required] public string Subject { get; set; }
[Required] public string Body { get; set; }
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
namespace LCU.API.Internal.MemberPreferencesApi.Models.Requests;
public class XdiBatchRequest
{
public List<XdiBatchItem> XdiBatchItems { get; set; }
public XdiBatch XdiBatch { get; set; }
public string SftpRootBasePath { get; set; }
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.Models.Requests;
public class XdiBatchRequests
{
public List<XdiBatchRequest> XdiBatchRequestList { get; set; }
}
@@ -0,0 +1,109 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Models;
public class XdiBatch
{
private readonly XdiBatchFile _addMemberElectronic;
private readonly XdiBatchFile _updateMemberEmail;
private readonly XdiBatchFile _updateMemberToElectronic;
private readonly XdiBatchFile _updateMemberToPrint;
public XdiBatch()
{
_addMemberElectronic = new XdiBatchFile()
{
Type = XdiFileType.AddMemberElectronic
};
_updateMemberEmail = new XdiBatchFile()
{
Type = XdiFileType.UpdateMemberEmail
};
_updateMemberToElectronic = new XdiBatchFile()
{
Type = XdiFileType.UpdateMemberToElectronic
};
_updateMemberToPrint = new XdiBatchFile()
{
Type = XdiFileType.UpdateMemberToPrint
};
}
public bool ProcessItem(XdiBatchItem item, MemberOptInSettings currentSettings)
{
if (currentSettings.UniversalOptIn == DeliveryPreference.Paper &&
currentSettings.ENotifications == DeliveryPreference.Electronic)
{
_updateMemberToPrint.AddRow(item.UserId, currentSettings.UnifiedOptInEmail);
return true;
}
if (currentSettings.UniversalOptIn == DeliveryPreference.Electronic &&
currentSettings.ENotifications != DeliveryPreference.Electronic)
{
if (currentSettings.ENotifications == DeliveryPreference.Paper)
{
_updateMemberToElectronic.AddRow(item.UserId, currentSettings.UnifiedOptInEmail);
return true;
}
_addMemberElectronic.AddRow(item.UserId, currentSettings.UnifiedOptInEmail);
return true;
}
if (currentSettings.UniversalOptIn == DeliveryPreference.Electronic &&
currentSettings.ENotifications == DeliveryPreference.Electronic &&
currentSettings.UnifiedOptInEmail != currentSettings.ENotificationsEmail)
{
_updateMemberEmail.AddRow(item.UserId, currentSettings.UnifiedOptInEmail);
return true;
}
if (currentSettings.UniversalOptIn == DeliveryPreference.Hold &&
currentSettings.ENotifications == DeliveryPreference.Electronic)
{
_updateMemberToPrint.AddRow(item.UserId, currentSettings.UnifiedOptInEmail);
return true;
}
if (currentSettings.UniversalOptIn == DeliveryPreference.Microfiche &&
currentSettings.ENotifications == DeliveryPreference.Electronic)
{
_updateMemberToPrint.AddRow(item.UserId, currentSettings.UnifiedOptInEmail);
return true;
}
return false;
}
public IReadOnlyCollection<XdiBatchFile> GetNonEmptyFiles(string pathPrefix = "")
{
var list = new List<XdiBatchFile>();
if (_updateMemberEmail.HasRows())
{
_updateMemberEmail.FolderPrefix = pathPrefix;
list.Add(_updateMemberEmail);
}
if (_addMemberElectronic.HasRows())
{
_addMemberElectronic.FolderPrefix = pathPrefix;
list.Add(_addMemberElectronic);
}
if (_updateMemberToElectronic.HasRows())
{
_updateMemberToElectronic.FolderPrefix = pathPrefix;
list.Add(_updateMemberToElectronic);
}
if (_updateMemberToPrint.HasRows())
{
_updateMemberToPrint.FolderPrefix = pathPrefix;
list.Add(_updateMemberToPrint);
}
return list;
}
}
@@ -0,0 +1,54 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using System.Text;
namespace LCU.API.Internal.MemberPreferencesApi.Models;
public class XdiBatchFile
{
private const string Ext = "LMCU.txt";
public XdiFileType Type { get; set; }
public List<XdiBatchFileRow> Data { get; set; } = new List<XdiBatchFileRow>();
public string FolderPrefix { get; set; } = "";
public string GetFileName()
{
switch (Type)
{
case XdiFileType.AddMemberElectronic: return $"{FolderPrefix}Enrollment_AddElectronic_{Ext}";
case XdiFileType.DeleteMember: return $"{FolderPrefix}Enrollment_Delete_{Ext}";
case XdiFileType.UpdateMemberEmail: return $"{FolderPrefix}Enrollment_UpdateEmail_{Ext}";
case XdiFileType.UpdateMemberToElectronic: return $"{FolderPrefix}Enrollment_UpdateToElectronic_{Ext}";
case XdiFileType.UpdateMemberToPrint: return $"{FolderPrefix}Enrollment_UpdateToPrint_{Ext}";
default: throw new Exception($"Unknown File Type in XDI BatchFile: {Type}");
}
}
public void AddRow(string userId, string email)
{
var row = new XdiBatchFileRow()
{
UserId = $"{userId}",
Email = email
};
Data.Add(row);
}
public IEnumerable<string> ConvertToDataRows()
{
return Data.Select(batchRow => batchRow.ConvertToRow(Type));
}
public byte[] ConvertToDataFile()
{
var allRows = ConvertToDataRows();
var file = string.Join("\n", allRows);
return Encoding.UTF8.GetBytes(file);
}
public bool HasRows()
{
return Data != null && Data.Any();
}
}
@@ -0,0 +1,23 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Models;
public class XdiBatchFileRow
{
public string UserId { get; set; }
public string Email { get; set; }
public string CellPhone { get; set; }
public string ConvertToRow(XdiFileType type)
{
switch (type)
{
case XdiFileType.UpdateMemberToPrint:
case XdiFileType.DeleteMember: return UserId + "|";
case XdiFileType.AddMemberElectronic:
case XdiFileType.UpdateMemberEmail:
case XdiFileType.UpdateMemberToElectronic: return UserId + "|" + Email + "|";
default: throw new Exception($"Unknown File Type in XDIBatchFileRow: {type}");
}
}
}
@@ -0,0 +1,328 @@
using LCU.Library.Internal.Logging.Models;
using LCU.Library.Internal.Logging;
using LCU.Library.Internal.ApiSecurity;
using LCU.Library.Internal.LcuHttpClient;
using LCU.Library.Internal.Status;
using LCU.Library.Internal.Configuration;
using LCU.Library.Internal.LcuMassTransit;
using LCU.Library.Internal.Logging.Middleware;
using LCU.API.Internal.MemberPreferencesApi.Consumers;
using LCU.API.Internal.MemberPreferencesApi.HostedServices;
using LCU.API.Internal.MemberPreferencesApiLib.Persistence;
using LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
using LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
using LCU.API.Internal.MemberPreferencesApi.Interfaces;
using LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
using LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
using LCU.API.Internal.MemberPreferencesApi.DataAccess;
using LCU.API.Internal.MemberPreferencesApi.Services;
using LCU.Library.Internal.MemberPreferencesApiModels.Models;
using LCU.API.Internal.MemberPreferencesApi.Models.EmailPolicies;
using LCU.API.Internal.MemberPreferencesApiLib.Repositories;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.LcuMassTransitModels;
using Infrastructure.Services;
using Microsoft.FeatureManagement;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using MassTransit;
string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = BuildBuilder(args);
BuildServicesAndConfigurations(builder);
BuildAppAndRun(builder);
WebApplicationBuilder BuildBuilder(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("/app/config/mapped-appsettings/primary.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-member-preferences-api-key.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-ftp-fingerprint.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-ftp-password.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-ftp-username.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-queue-password.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-queue-username.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-db-connection.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-environment-from-vault.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/member-preferences-api-seq-api-key.json", optional: true, reloadOnChange: true);
builder.Host.UseSerilog();
return builder;
}
void BuildServicesAndConfigurations(WebApplicationBuilder builder)
{
var services = builder.Services;
var configuration = builder.Configuration;
var apiConfiguration = configuration.Bind<ApiConfiguration>(services, "Primary");
configuration.Bind<XdiSftpApiConfiguration>(services);
configuration.Bind<FtpApiConfiguration>(services);
configuration.Bind<NotificationApiConfiguration>(services);
configuration.Bind<CoreApiWrapperConfiguration>(services);
configuration.Bind<DnaAuditProcessConfiguration>(services);
var loggerConfiguration = new LoggingConfiguration(apiConfiguration.AppName,
apiConfiguration.SeqKey,
apiConfiguration.SeqUrl);
var lcuLogging = new LcuLogging(loggerConfiguration);
lcuLogging.UseLcuLogging();
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins(
apiConfiguration.CorsAllowedOrigins
).AllowAnyHeader().AllowAnyMethod();
});
});
services.AddHttpContextAccessor();
services.AddHttpClient();
var databaseSettings = configuration.Bind<SqlConnectionConfiguration>(services, "SqlConnectionConfiguration");
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer
(
databaseSettings.ConnectionString,
sql => sql.EnableRetryOnFailure(databaseSettings.MaxRetryCount, databaseSettings.MaxRetryDelay, null)
);
});
services.AddLCUStatusServicesWithApiHealthCheck(configuration);
services.AddLCUHttpServices();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = $"{apiConfiguration.AppName}", Version = "v1" });
});
if (apiConfiguration.FeatureFlags.RunHostedServices)
{
services.AddHostedService<HostedXdiBatchProcessService>();
services.AddHostedService<HostedDnaDataAuditProcessService>();
}
services.AddFeatureManagement(configuration.GetSection("Primary:FeatureGates"));
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
services.AddScoped<IAuditableChanges>(provider => provider.GetService<ApplicationDbContext>());
services.AddTransient<IDateTime, DateTimeService>();
services.AddApiKeySecurity(configuration);
var connectionConfiguration = MassTransitConfigurationSetUp.AddConnectionConfiguration(ref services, configuration);
var receiveEndpointConfigurations = new ReceiveEndpointConfigurations()
{
XdiBatchProcessEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "XdiBatchProcessEndpoint"),
XdiBatchConfirmEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "XdiBatchConfirmEndpoint"),
DnaAccountAuditEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "DnaAccountAuditEndpoint"),
DnaAgreementAuditEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "DnaAgreementAuditEndpoint"),
DnaOrganizationAuditEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "DnaOrganizationAuditEndpoint"),
};
services.AddSingleton(receiveEndpointConfigurations);
AddMassTransitServices(ref services, connectionConfiguration, receiveEndpointConfigurations, apiConfiguration);
AddMemberPreferencesApiServices(services);
AddMemberPreferencesDaos(services);
AddMemberPreferencesLib(ref services);
}
void AddMassTransitServices(ref IServiceCollection services,
ConnectionConfiguration connectionConfiguration,
ReceiveEndpointConfigurations receiveEndpointConfigurations,
ApiConfiguration configuration)
{
services.AddMassTransit(x =>
{
x.AddConsumer<XdiBatchProcessConsumer>();
x.AddConsumer<XdiSshResponseConsumer>();
x.AddConsumer<AccountDnaFieldChangeConsumer>();
x.AddConsumer<AgreementDnaFieldChangeConsumer>();
x.AddConsumer<OrganizationDnaFieldChangeConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host(connectionConfiguration.HostUrl, "/", host =>
{
host.Username(connectionConfiguration.UserName);
host.Password(connectionConfiguration.Password);
});
if (configuration.FeatureFlags.RunXdiBatchProcessConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.XdiBatchProcessEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<XdiBatchProcessConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.XdiBatchProcessEndpoint.PrefetchLimit;
});
}
if (configuration.FeatureFlags.RunXdiSshResponseConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.XdiBatchConfirmEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<XdiSshResponseConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.XdiBatchConfirmEndpoint.PrefetchLimit;
});
}
if (configuration.FeatureFlags.RunAccountDnaFieldChangeConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.DnaAccountAuditEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<AccountDnaFieldChangeConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.DnaAccountAuditEndpoint.PrefetchLimit;
});
}
if (configuration.FeatureFlags.RunAgreementDnaFieldChangeConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.DnaAgreementAuditEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<AgreementDnaFieldChangeConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.DnaAgreementAuditEndpoint.PrefetchLimit;
});
}
if (configuration.FeatureFlags.RunOrganizationDnaFieldChangeConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.DnaOrganizationAuditEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<OrganizationDnaFieldChangeConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.DnaOrganizationAuditEndpoint.PrefetchLimit;
});
}
});
});
services.AddMassTransitHostedService();
}
void AddMemberPreferencesApiServices(IServiceCollection services)
{
services.AddScoped<IOptinService, OptinService>();
services.AddScoped<IMemberEmailService, MemberEmailService>();
services.AddScoped<IMemberPreferenceService, MemberPreferenceService>();
services.AddScoped<IIntegrationService, IntegrationService>();
AddDnaAuditServices(services);
AddXdiServices(services);
AddEmailUndeliverableServices(services);
AddQueueServices(services);
AddMicroApiServices(services);
}
void AddDnaAuditServices(IServiceCollection services)
{
services.AddScoped<IDNADataAuditService, DNADataAuditService>();
services.AddScoped<IDNADataAuditProcessService, DNADataAuditProcessService>();
}
void AddXdiServices(IServiceCollection services)
{
services.AddScoped<IXdiBatchService, XdiBatchService>();
services.AddScoped<IXdiBatchProcessService, XdiBatchProcessService>();
}
void AddEmailUndeliverableServices(IServiceCollection services)
{
services.AddScoped<EStatementsSixtyDaysUndeliverableEmailPolicy>();
services.AddScoped<UnifiedSixtyDaysUndeliverableEmailPolicy>();
services.AddTransient<IEnumerable<IEmailFailurePolicy>>(p => new IEmailFailurePolicy[]
{
p.GetRequiredService<EStatementsSixtyDaysUndeliverableEmailPolicy>(),
p.GetRequiredService<UnifiedSixtyDaysUndeliverableEmailPolicy>()
});
}
void AddQueueServices(IServiceCollection services)
{
services.AddScoped<IQueueDnaAccountAuditService, QueueDnaAccountAuditService>();
services.AddScoped<IQueueDnaOrganizationAuditService, QueueDnaOrganizationService>();
services.AddScoped<IQueueDnaAgreementAuditService, QueueDnaAgreementAuditService>();
services.AddScoped<IQueueXdiBatchService, QueueXdiBatchService>();
}
void AddMicroApiServices(IServiceCollection services)
{
services.AddScoped<ICoreApiWrapperService, CoreApiWrapperService>();
services.AddScoped<IFtpApiService, FtpApiService>();
services.AddScoped<INotificationService, NotificationService>();
}
void AddMemberPreferencesDaos(IServiceCollection services)
{
services.AddScoped<INotificationApiDao, NotificationApiDao>();
services.AddScoped<IFtpApiDao, FtpApiDao>();
services.AddScoped<ICoreApiWrapperDao, CoreApiWrapperDao>();
}
void AddMemberPreferencesLib(ref IServiceCollection services)
{
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IUnifiedOptInRepository, UnifiedOptInRepository>();
services.AddScoped<IMemberPreferenceRepository, MemberPreferenceRepository>();
services.AddScoped<IMemberPreferences, MemberPreferenceRepository>();
services.AddScoped<IXdiBatchItemsRepository, XdiBatchItemsRepository>();
services.AddScoped<IDnaAuditProcessRepository, DnaAuditProcessRepository>();
}
void BuildAppAndRun(WebApplicationBuilder builder)
{
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((swaggerDoc, request) =>
{
const string prefixHeader = "X-Forwarded-Prefix";
if (!request.Headers.ContainsKey(prefixHeader)) return;
var serverUrl = request.Headers[prefixHeader];
swaggerDoc.Servers = new List<OpenApiServer>()
{
new() { Description = serverUrl, Url = serverUrl }
};
});
});
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "swagger";
c.SwaggerEndpoint("v1/swagger.json", "LCU.API.Internal.MemberPreferencesApi v1");
});
app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest;
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors(MyAllowSpecificOrigins);
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
app.Run();
}
@@ -0,0 +1,54 @@
using LCU.API.Internal.MemberPreferencesApi.Interfaces;
namespace Infrastructure.Services;
public class DateTimeService : IDateTime
{
public DateTime Now => DateTime.Now;
public DateTime UTCNow => DateTime.UtcNow;
public DateTime? CSTWindowsNow
{
get
{
try
{
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
return TimeZoneInfo.ConvertTimeFromUtc(UTCNow, easternZone);
}
catch (Exception)
{
return null;
}
}
}
public DateTime? CSTLinuxNow
{
get
{
try
{
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("America/Chicago");
return TimeZoneInfo.ConvertTimeFromUtc(UTCNow, easternZone);
}
catch (Exception)
{
return null;
}
}
}
public DateTime ConvertDateTimeFromCSTToUTCLinux(string dateTimeString)
{
var date = DateTime.Parse(dateTimeString);
return TimeZoneInfo.ConvertTime(date, TimeZoneInfo.FindSystemTimeZoneById("America/Chicago"), TimeZoneInfo.Utc);
}
public DateTime ConvertDateTimeFromCSTToUTCWindows(string dateTimeString)
{
var date = DateTime.Parse(dateTimeString);
return TimeZoneInfo.ConvertTime(date, TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time"), TimeZoneInfo.Utc);
}
}
@@ -0,0 +1,157 @@
using LCU.API.Internal.MemberPreferencesApi.Interfaces;
using LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
using LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
using LCU.API.Internal.MemberPreferencesApiLib.Repositories;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
public class DNADataAuditProcessService : IDNADataAuditProcessService
{
private readonly IDateTime _dateTime;
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IDnaAuditProcessRepository _dnaAuditProcessRepository;
private readonly DnaAuditProcessConfiguration _dnaAuditProcessConfig;
private readonly IQueueDnaAccountAuditService _queueDnaAccountAuditService;
private readonly IQueueDnaAgreementAuditService _queueDnaAgreementAuditService;
private readonly IQueueDnaOrganizationAuditService _queueDnaOrganizationAuditService;
public DNADataAuditProcessService(IQueueDnaAccountAuditService queueDnaAccountAuditService,
IQueueDnaAgreementAuditService queueDnaAgreementAuditService,
IQueueDnaOrganizationAuditService queueDnaOrganizationAuditService,
ICoreApiWrapperService coreApiWrapperService, IDateTime dateTime,
IDnaAuditProcessRepository dnaAuditProcessRepository, DnaAuditProcessConfiguration dnaAuditProcessConfig)
{
_queueDnaAccountAuditService = queueDnaAccountAuditService;
_queueDnaAgreementAuditService = queueDnaAgreementAuditService;
_queueDnaOrganizationAuditService = queueDnaOrganizationAuditService;
_coreApiWrapperService = coreApiWrapperService;
_dateTime = dateTime;
_dnaAuditProcessRepository = dnaAuditProcessRepository;
_dnaAuditProcessConfig = dnaAuditProcessConfig;
}
public async Task RunDataAuditProcesses()
{
try
{
var dnaProcesses = await _dnaAuditProcessRepository.GetAllDnaAuditProcesses();
foreach (var dnaProcess in dnaProcesses)
{
var timeToSearchFrom = dnaProcess.LastUpdated.AddMinutes(_dnaAuditProcessConfig.LookBackMinutes);
switch (dnaProcess.DnaProcessType)
{
case DnaAuditProcessType.DNAOrganization:
await AuditDNAOrganizationForTimePeriod(timeToSearchFrom, dnaProcess);
break;
case DnaAuditProcessType.DNAAgreement:
await AuditDNAAgreementForTimePeriod(timeToSearchFrom, dnaProcess);
break;
case DnaAuditProcessType.DNAAccounts:
await AuditDNAAccountsForTimePeriod(timeToSearchFrom, dnaProcess);
break;
default:
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error($"DnaAuditProcessType not found for ProcessId: {dnaProcess.ProcessId}. ProcessType: {dnaProcess.DnaProcessType}");
break;
}
}
}
catch (Exception ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(ex, "Failed to Queue Last 5 Minutes of MemberPreference DNA DataAudit Activity");
}
}
public async Task AuditDNAOrganizationForTimePeriod(DateTime timeToSearchFrom, DnaAuditProcess dnaProcess)
{
try
{
var dnaOrganizationAuditRecords = await _coreApiWrapperService.GetAuditOrganizationFromDateLastMaintained(timeToSearchFrom);
var organizationAuditRecords = dnaOrganizationAuditRecords.ToList();
await _queueDnaOrganizationAuditService.QueueDataAuditForOrganizations(organizationAuditRecords, dnaProcess.ConsecutiveFailures);
Log.Information($"Queued {organizationAuditRecords.Count} DataAudit Organization Changes Since Last DNADataAuditProducerService Run.");
dnaProcess = HandleDnaAuditProcessSuccess(dnaProcess);
}
catch (Exception ex)
{
dnaProcess = HandleDnaAuditProcessException(dnaProcess, ex);
}
await _dnaAuditProcessRepository.Update(dnaProcess);
}
public async Task AuditDNAAgreementForTimePeriod(DateTime timeToSearchFrom, DnaAuditProcess dnaProcess)
{
try
{
var dnaAgreementAuditRecords = await _coreApiWrapperService.GetAuditAgreementFromDateLastMaintained(timeToSearchFrom);
var agreementAuditRecords = dnaAgreementAuditRecords.ToList();
await _queueDnaAgreementAuditService.QueueDataAuditForAgreements(agreementAuditRecords, dnaProcess.ConsecutiveFailures);
Log.Information($"Queued {agreementAuditRecords.Count} DataAudit Agreement Changes Since Last DNADataAuditProducerService Run.");
dnaProcess = HandleDnaAuditProcessSuccess(dnaProcess);
}
catch (Exception ex)
{
dnaProcess = HandleDnaAuditProcessException(dnaProcess, ex);
}
await _dnaAuditProcessRepository.Update(dnaProcess);
}
public async Task AuditDNAAccountsForTimePeriod(DateTime timeToSearchFrom, DnaAuditProcess dnaProcess)
{
try
{
var dnaAccountAuditRecords = await _coreApiWrapperService.GetAuditAccountsFromDateLastMaintained(timeToSearchFrom);
var accountAuditRecords = dnaAccountAuditRecords.ToList();
await _queueDnaAccountAuditService.QueueDataAuditForAccounts(accountAuditRecords, dnaProcess.ConsecutiveFailures);
Log.Information($"Queued {accountAuditRecords.Count} DataAudit Account Changes Since Last DNADataAuditProducerService Run.");
dnaProcess = HandleDnaAuditProcessSuccess(dnaProcess);
}
catch (Exception ex)
{
dnaProcess = HandleDnaAuditProcessException(dnaProcess, ex);
}
await _dnaAuditProcessRepository.Update(dnaProcess);
}
public DnaAuditProcess HandleDnaAuditProcessException(DnaAuditProcess process, Exception ex)
{
process.ConsecutiveFailures++;
if (process.ConsecutiveFailures >= _dnaAuditProcessConfig.NumberOfAllowedConsecutiveFailures)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error($"DnaAuditProcess for {process.DnaProcessType} has failed {process.ConsecutiveFailures} consecutive times. " +
$"Last succesful runtime: {process.LastUpdated} CST. " +
$"This process needs to checked on to determine why failure is occuring.");
}
else
{
Log.Warning($"Exception and Failure occured for DnaAuditProcess: {process.DnaProcessType}. ConsecutiveFailures: {process.ConsecutiveFailures}. " +
$"Last succesful runtime: {process.LastUpdated} CST. Exception: {ex}");
}
return process;
}
public DnaAuditProcess HandleDnaAuditProcessSuccess(DnaAuditProcess process)
{
var utcTime = _dateTime.UTCNow;
var easternZone = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Chicago");
var easternTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, easternZone);
process.LastUpdated = easternTime;
process.ConsecutiveFailures = 0;
return process;
}
}
@@ -0,0 +1,161 @@
using LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Agreements;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
namespace LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
public class DNADataAuditService : IDNADataAuditService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IOptinService _optinService;
private const string MemberPreferencePrint = "PRNT";
private const string MemberPreferenceElectronic = "WWW";
public DNADataAuditService(ICoreApiWrapperService coreApiWrapperService, IOptinService optinService)
{
_optinService = optinService;
_coreApiWrapperService = coreApiWrapperService;
}
public async Task HandleAccountAuditCheck(DnaFieldChangeRequest request)
{
try
{
var accountNumber = request.DnaFieldIdInformation;
var account = await _coreApiWrapperService.GetAgreementAccountByAccountId(accountNumber);
if (account == null)
{
Log.Information($"During audit process account with ID: {accountNumber} could not be found, and will not be processed.");
return;
}
await UpdateAccountDeliveryMethodIfNotEqualToMemberPreference(account);
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to process DNA Audit for member preferences Account.");
}
}
public async Task HandleAgreementAuditCheck(DnaFieldChangeRequest request)
{
try
{
var agreementNumber = request.DnaFieldIdInformation;
var agreement = await _coreApiWrapperService.GetAgreementByAgreementId(agreementNumber);
if (agreement == null)
{
Log.Information($"During audit process agreement with ID: {agreementNumber} could not be found, and will not be processed.");
return;
}
IEnumerable<AgreementAccount> agreementAccounts;
if (agreement.ByOrgNbr != 0)
{
agreementAccounts = await _coreApiWrapperService.GetAgreementAccountsByOrganizationId(agreement.ByOrgNbr);
}
else
{
agreementAccounts = await _coreApiWrapperService.GetAgreementAccountsByUserId(agreement.UserId);
agreementAccounts = agreementAccounts.Where(account => account.TaxReportForPersonNumber != null && account.AccountOwner.AgreementNumber == agreement.AgreementNumber);
}
foreach (var agreementAccount in agreementAccounts)
{
await UpdateAccountDeliveryMethodIfNotEqualToMemberPreference(agreementAccount);
}
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to process DNA Audit for member preferences Agreement.");
}
}
public async Task HandleOrganizationAuditCheck(DnaFieldChangeRequest request)
{
try
{
var organizationId = request.DnaFieldIdInformation;
var organizationAccounts = await _coreApiWrapperService.GetAgreementAccountsByOrganizationId(organizationId);
if (organizationAccounts == null)
{
Log.Information($"During audit process organization with ID: {organizationId} could not be found, and will not be processed.");
return;
}
foreach (var account in organizationAccounts)
{
await UpdateAccountDeliveryMethodIfNotEqualToMemberPreference(account);
}
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to process DNA Audit for member preferences Organization.");
}
}
private async Task UpdateAccountDeliveryMethodIfNotEqualToMemberPreference(AgreementAccount account)
{
try
{
if (account.AccountOwner?.UserId == null)
{
await _coreApiWrapperService.UpdateAgreementAccountsDeliveryMethod(new List<long> { account.AccountNumber }, "PRNT", "None Found During Audit");
return;
}
var currentAccountDeliveryMethod = account.OvrDeliveryMethCD;
var memberOptinSettings = await _optinService.GetMemberOptInSettings(account.AccountOwner.UserId);
var memberPreferenceDeliveryMethod = DetermineMemberPreference(memberOptinSettings);
if (!string.Equals(currentAccountDeliveryMethod, memberPreferenceDeliveryMethod, StringComparison.InvariantCultureIgnoreCase))
{
await _coreApiWrapperService.UpdateAgreementAccountsDeliveryMethod(new List<long> { account.AccountNumber }, memberPreferenceDeliveryMethod, account.AccountOwner.UserId);
}
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to UpdateAccountDeliveryMethodIfNotEqualToMemberPreference for account number: {account.AccountNumber}");
}
}
private string DetermineMemberPreference(MemberOptInSettings optinSettings)
{
if (optinSettings == null)
{
return MemberPreferencePrint;
}
if (optinSettings.IsUnifiedElectronicEnrolled())
{
return MemberPreferenceElectronic;
}
if (optinSettings.IsUnifiedPaperEnrolled())
{
return MemberPreferencePrint;
}
if (string.Equals(optinSettings.EStatements, "Paper", StringComparison.InvariantCultureIgnoreCase))
{
return MemberPreferencePrint;
}
if (string.Equals(optinSettings.EStatements, "Electronic", StringComparison.InvariantCultureIgnoreCase))
{
return MemberPreferenceElectronic;
}
return MemberPreferencePrint;
}
}
@@ -0,0 +1,11 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
namespace LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
public interface IDNADataAuditProcessService
{
Task RunDataAuditProcesses();
Task AuditDNAOrganizationForTimePeriod(DateTime timeToSearchFrom, DnaAuditProcess dnaProcess);
Task AuditDNAAgreementForTimePeriod(DateTime timeToSearchFrom, DnaAuditProcess dnaProcess);
Task AuditDNAAccountsForTimePeriod(DateTime timeToSearchFrom, DnaAuditProcess dnaProcess);
}
@@ -0,0 +1,8 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
public interface IDNADataAuditService
{
Task HandleAccountAuditCheck(DnaFieldChangeRequest dnaFieldChange);
Task HandleAgreementAuditCheck(DnaFieldChangeRequest dnaFieldChange);
Task HandleOrganizationAuditCheck(DnaFieldChangeRequest dnaFieldChange);
}
@@ -0,0 +1,56 @@
using LCU.API.Internal.MemberPreferencesApi.Services.DnaAuditServices;
using NCrontab;
namespace LCU.API.Internal.MemberPreferencesApi.HostedServices
{
public class HostedDnaDataAuditProcessService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private CancellationToken _cancellationToken;
private ApiConfiguration _memberPreferencesConfig;
private readonly CrontabSchedule _cleanUpJobSchedule;
private DateTime _nextScheduleRun;
public HostedDnaDataAuditProcessService(ApiConfiguration appConfig,
IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
_memberPreferencesConfig = appConfig;
_cleanUpJobSchedule = CrontabSchedule.Parse(_memberPreferencesConfig.DnaDataAuditProcessCronSchedule);
_nextScheduleRun = _cleanUpJobSchedule.GetNextOccurrence(DateTime.Now);
_cancellationToken = new CancellationToken();
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
Log.Information("Hosted DNADataAuditProcessService is starting, for DNA Member Preferences Audit.");
_cancellationToken = cancellationToken;
try
{
while (!cancellationToken.IsCancellationRequested)
{
if (DateTime.Now > _nextScheduleRun)
{
using (var scope = _scopeFactory.CreateScope())
{
await scope.ServiceProvider.GetRequiredService<IDNADataAuditProcessService>().RunDataAuditProcesses();
}
}
await Task.Delay(UntilNextExecution(), cancellationToken);
}
}
catch (TaskCanceledException ex) when (cancellationToken.IsCancellationRequested)
{
Log.Information(ex, "Hosted DnaDataAuditProcessService was shut down normally.");
}
catch (Exception ex)
{
Log.Error(ex, "Error in main thread for DnaDataAuditProcessService");
}
Log.Information("Hosted DnaDataAuditProcessService is stopping.");
}
private int UntilNextExecution() => Math.Max(0, (int)_nextScheduleRun.Subtract(DateTime.Now).TotalMilliseconds);
}
}
@@ -0,0 +1,57 @@
using LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
using NCrontab;
namespace LCU.API.Internal.MemberPreferencesApi.HostedServices;
public class HostedXdiBatchProcessService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private CancellationToken _cancellationToken;
private ApiConfiguration _apiConfiguration;
private XdiSftpApiConfiguration _xdiSftpApiConfiguration;
private readonly CrontabSchedule _cleanUpJobSchedule;
private DateTime _nextScheduleRun;
public HostedXdiBatchProcessService(ApiConfiguration apiConfiguration, XdiSftpApiConfiguration xdiSftpApiConfiguration, IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
_apiConfiguration = apiConfiguration;
_xdiSftpApiConfiguration = xdiSftpApiConfiguration;
_cleanUpJobSchedule = CrontabSchedule.Parse(_apiConfiguration.XdiBatchProcessCronSchedule);
_nextScheduleRun = _cleanUpJobSchedule.GetNextOccurrence(DateTime.Now);
_cancellationToken = new CancellationToken();
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
Log.Information("Hosted XDI Batch Service is starting");
_cancellationToken = cancellationToken;
try
{
while (!cancellationToken.IsCancellationRequested)
{
if (DateTime.Now > _nextScheduleRun)
{
using (var scope = _scopeFactory.CreateScope())
{
await scope.ServiceProvider.GetRequiredService<IXdiBatchProcessService>().RunXdiBatchProcess(_xdiSftpApiConfiguration.SftpRootBasePath);
}
_nextScheduleRun = _cleanUpJobSchedule.GetNextOccurrence(DateTime.Now);
}
await Task.Delay(UntilNextExecution(), cancellationToken);
}
}
catch (TaskCanceledException ex) when (cancellationToken.IsCancellationRequested)
{
Log.Information(ex, "Hosted XdiBatchService was shut down normally");
}
catch (Exception ex)
{
Log.Error(ex, "Error in main thread for XdiBatchService");
}
Log.Information("Hosted XdiBatchService is stopping");
}
private int UntilNextExecution() => Math.Max(0, (int)_nextScheduleRun.Subtract(DateTime.Now).TotalMilliseconds);
}
@@ -0,0 +1,11 @@
namespace LCU.API.Internal.MemberPreferencesApi.Interfaces;
public interface IDateTime
{
DateTime Now { get; }
DateTime UTCNow { get; }
DateTime? CSTLinuxNow { get; }
DateTime? CSTWindowsNow { get; }
DateTime ConvertDateTimeFromCSTToUTCWindows(string dateTimeString);
DateTime ConvertDateTimeFromCSTToUTCLinux(string dateTimeString);
}
@@ -0,0 +1,8 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public interface IIntegrationService
{
Task NotifyListeners(MemberOptInSettings request);
}
@@ -0,0 +1,7 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public interface IMemberEmailService
{
Task<bool> EmailSucceededToSend(string userId, string email);
Task<bool> EmailFailedToSend(string userId, string email, bool enablePolicy);
}
@@ -0,0 +1,15 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public interface IMemberPreferenceService
{
Task<IReadOnlyCollection<MemberPreference>> GetMemberPreferencesAsync(string userId);
IReadOnlyCollection<string> GetAvailableTypes();
Task<MemberOptInSettings> SaveMemberPreferenceAsync(MemberPreferenceRequest preference);
Task<MemberOptInSettings> SaveMemberPreferencesAsync(List<MemberPreferenceRequest> preferences);
Task<MemberOptInSettings> SaveMemberPreferenceDefaultAsync(MemberPreferenceRequest preferences, string operatorId, UserType userType, DateTime dateAdded);
Task UpdateMemberOptinAsync(MemberOptinRequest optinRequest);
Task UpdateAgreementDeliveryMethodCode(string userId, string deliveryMethod);
}
@@ -0,0 +1,14 @@
using LCU.Library.Internal.MemberPreferencesApiModels.DataTransfer;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public interface IOptinService
{
Task<MemberOptInSettings> GetMemberOptInSettings(string userId);
Task<MemberOptInSettings> GetMemberOptInSettingsOrCreateDefaultSetting(string userId);
Task<IReadOnlyCollection<MemberOptInAgreementInformation>> GetMemberOptInAgreementInformationByAccountId(long accountId);
Task<MemberOptInAgreementInformation> GetMemberOptInAgreementInformationForUser(string userId);
Task<IReadOnlyCollection<MemberOptInInformation>> TransformMemberOptInSettingsForClient(List<MemberOptInSettings> memberOptInSettingsList);
Task<MemberOptInInformation> TransformMemberOptInSettingsForClient(MemberOptInSettings memberOptInSettings);
}
@@ -0,0 +1,32 @@
using LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public class IntegrationService : IIntegrationService
{
private readonly IXdiBatchService _xdiBatchItemService;
public IntegrationService(IXdiBatchService xdiBatchItemService)
{
_xdiBatchItemService = xdiBatchItemService;
}
public async Task NotifyListeners(MemberOptInSettings request)
{
await NotifyXDI(request);
}
private async Task NotifyXDI(MemberOptInSettings request)
{
var newItem = new XdiBatchItem()
{
UserId = request.UserId,
Created = DateTime.Now,
BatchState = BatchState.New,
LastUpdated = DateTime.Now
};
await _xdiBatchItemService.AddXdiBatchItem(newItem);
}
}
@@ -0,0 +1,181 @@
using LCU.API.Internal.MemberPreferencesApiLib.Repositories;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using LCU.API.Internal.MemberPreferencesApi.Models.EmailPolicies;
using Microsoft.AspNetCore.Identity;
namespace LCU.API.Internal.MemberPreferencesApi.Services
{
public class MemberEmailService : IMemberEmailService
{
private readonly IEnumerable<IEmailFailurePolicy> _failurePolicies;
private readonly IUnifiedOptInRepository _unifiedOptInRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IMemberPreferenceService _memberPreferenceService;
private readonly IIntegrationService _integrationService;
public MemberEmailService(IUnifiedOptInRepository unifiedOptInRepository, IUnitOfWork unitOfWork, IEnumerable<IEmailFailurePolicy> failurePolicies,
IMemberPreferenceService memberPreferenceService, IIntegrationService integrationService)
{
_unifiedOptInRepository = unifiedOptInRepository;
_unitOfWork = unitOfWork;
_failurePolicies = failurePolicies;
_memberPreferenceService = memberPreferenceService;
_integrationService = integrationService;
}
public async Task<bool> EmailSucceededToSend(string userId, string email)
{
var userOptin = await _unifiedOptInRepository.GetMemberOptInSettings(userId);
if (userOptin is null) return false;
var updated = false;
if (EmailMatches(email, userOptin.UnifiedOptInEmail))
{
UnifiedOptInEmailSucceededToSend(userOptin);
updated = true;
}
if (EmailMatches(email, userOptin.ENotificationsEmail))
{
ENotificationsEmailSucceededToSend(userOptin);
updated = true;
}
if (EmailMatches(email, userOptin.EStatementsEmail))
{
EStatementsEmailSucceededToSend(userOptin);
updated = true;
}
if (updated)
{
await _unitOfWork.CompleteAsync();
}
return updated;
}
public async Task<bool> EmailFailedToSend(string userId, string email, bool enablePolicy)
{
var userOptin = await _unifiedOptInRepository.GetMemberOptInSettings(userId);
if (userOptin == null) return false;
var updated = false;
var isBounceBack = false;
if (userOptin.HasAnyUnifiedPreference())
{
if (EmailMatches(email, userOptin.UnifiedOptInEmail))
{
UnifiedOptInEmailFailedToSend(userOptin);
updated = true;
}
}
else
{
if (EmailMatches(email, userOptin.ENotificationsEmail))
{
ENotificationsEmailFailedToSend(userOptin);
updated = true;
}
if (EmailMatches(email, userOptin.EStatementsEmail))
{
EStatementsEmailFailedToSend(userOptin);
updated = true;
}
}
foreach (var policy in _failurePolicies)
{
if (await policy.Apply(userOptin, enablePolicy))
{
updated = true;
isBounceBack = true;
}
}
if (updated)
{
await _unitOfWork.CompleteAsync();
}
if (isBounceBack)
{
await _memberPreferenceService.UpdateAgreementDeliveryMethodCode(userId, "PRNT");
await _integrationService.NotifyListeners(userOptin);
}
return updated;
}
private static bool EmailMatches(string email, string otherEmail)
{
if (email is null) return otherEmail is null;
return email.Equals(otherEmail, StringComparison.OrdinalIgnoreCase);
}
private static void UnifiedOptInEmailSucceededToSend(MemberOptInSettings userOptin)
{
UnifiedOptInEmailAttempted(userOptin);
userOptin.UnifiedOptInEmailStatus = EmailStatus.DeliveredSuccessfully;
userOptin.UnifiedOptInEmailFirstSent = null;
userOptin.UnifiedOptInEmailStatusCount = 0;
}
private static void UnifiedOptInEmailFailedToSend(MemberOptInSettings userOptin)
{
UnifiedOptInEmailAttempted(userOptin);
userOptin.UnifiedOptInEmailStatus = EmailStatus.FailingDelivery;
userOptin.UnifiedOptInEmailFirstSent ??= DateTime.Now;
userOptin.UnifiedOptInEmailStatusCount++;
}
private static void UnifiedOptInEmailAttempted(MemberOptInSettings userOptin)
{
userOptin.UnifiedOptInEmailLastSent = DateTime.Now;
}
private static void EStatementsEmailSucceededToSend(MemberOptInSettings userOptin)
{
EStatementsEmailAttempted(userOptin);
userOptin.EStatementsEmailStatus = EmailStatus.DeliveredSuccessfully;
userOptin.EStatementsEmailFirstSent = null;
userOptin.EStatementsEmailStatusCount = 0;
}
private static void EStatementsEmailFailedToSend(MemberOptInSettings userOptin)
{
EStatementsEmailAttempted(userOptin);
userOptin.EStatementsEmailStatus = EmailStatus.FailingDelivery;
userOptin.EStatementsEmailFirstSent ??= DateTime.Now;
userOptin.EStatementsEmailStatusCount++;
}
private static void EStatementsEmailAttempted(MemberOptInSettings userOptin)
{
userOptin.EStatementsEmailLastSent = DateTime.Now;
}
private static void ENotificationsEmailSucceededToSend(MemberOptInSettings userOptin)
{
ENotificationsEmailAttempted(userOptin);
userOptin.ENotificationsEmailStatus = EmailStatus.DeliveredSuccessfully;
userOptin.ENotificationsEmailFirstSent = null;
userOptin.ENotificationsEmailStatusCount = 0;
}
private static void ENotificationsEmailFailedToSend(MemberOptInSettings userOptin)
{
ENotificationsEmailAttempted(userOptin);
userOptin.ENotificationsEmailStatus = EmailStatus.FailingDelivery;
userOptin.ENotificationsEmailFirstSent ??= DateTime.Now;
userOptin.ENotificationsEmailStatusCount++;
}
private static void ENotificationsEmailAttempted(MemberOptInSettings userOptin)
{
userOptin.ENotificationsEmailLastSent = DateTime.Now;
}
}
}
@@ -0,0 +1,326 @@
using LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
using LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
using LCU.API.Internal.MemberPreferencesApiLib.Repositories;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Agreements;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public class MemberPreferenceService : IMemberPreferenceService
{
private readonly IMemberPreferenceRepository _memberPreferenceRepository;
private readonly IUnifiedOptInRepository _unifiedOptInRepository;
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IUnitOfWork _unitOfWork;
private readonly INotificationService _notificationService;
private readonly IIntegrationService _integrationService;
private readonly IXdiBatchService _xdiBatchItemService;
private List<long> _accountNumbers;
public MemberPreferenceService(IMemberPreferenceRepository memberPreferenceRepository, IUnitOfWork unitOfWork,
IUnifiedOptInRepository unifiedOptInRepository, INotificationService notificationService,
IIntegrationService integrationService, IXdiBatchService xdiBatchItemService, ICoreApiWrapperService coreApiWrapperService)
{
_memberPreferenceRepository = memberPreferenceRepository;
_unitOfWork = unitOfWork;
_unifiedOptInRepository = unifiedOptInRepository;
_notificationService = notificationService;
_integrationService = integrationService;
_xdiBatchItemService = xdiBatchItemService;
_coreApiWrapperService = coreApiWrapperService;
}
public async Task<MemberOptInSettings> SaveMemberPreferenceAsync(MemberPreferenceRequest preference)
{
return await SaveMemberPreferencesAsync(new List<MemberPreferenceRequest>()
{
preference
});
}
public async Task<MemberOptInSettings> SaveMemberPreferencesAsync(List<MemberPreferenceRequest> preferences)
{
if (preferences == null || !preferences.Any())
{
throw new ArgumentException("No preferences passed to SaveMemberPreferencesAsync");
}
var userId = preferences.FirstOrDefault()?.UserId;
if (await _xdiBatchItemService.DoesMemberHavePendingXdiChange(userId))
{
throw new Exception();
}
var settings = await _unifiedOptInRepository.GetOrCreateMemberOptinSettings(userId);
foreach (var preference in preferences)
{
var memberPreference = new MemberPreference()
{
Preference = preference.Preference,
UserId = preference.UserId,
Value = preference.Value,
LegalReference = preference.LegalReference
};
await _memberPreferenceRepository.SaveMemberPreferenceAsync(memberPreference);
settings.Apply(memberPreference);
}
await _unitOfWork.CompleteAsync();
if (preferences.Any(preference => preference.Preference == PreferenceType.UnifiedOptIn && string.Equals(preference.Value, "Paper", StringComparison.InvariantCultureIgnoreCase)))
{
await UpdateAgreementDeliveryMethodCode(userId, "PRNT");
}
else if (preferences.Any(preference => preference.Preference == PreferenceType.UnifiedOptIn && string.Equals(preference.Value, "Electronic", StringComparison.InvariantCultureIgnoreCase)))
{
await UpdateAgreementDeliveryMethodCode(userId, "WWW");
}
return await _unifiedOptInRepository.GetMemberOptInSettings(userId);
}
public async Task<MemberOptInSettings> SaveMemberPreferenceDefaultAsync(MemberPreferenceRequest preference, string operatorId, UserType userType, DateTime dateAdded)
{
if (preference == null)
{
throw new ArgumentException("No preferences passed to SaveMemberPreferencesAsync");
}
var userId = preference.UserId;
var settings = await _unifiedOptInRepository.GetOrCreateMemberOptinSettings(userId);
if (!IsExternalUpdateNecessary(settings, preference))
{
var memberPreference = new MemberPreference()
{
Preference = preference.Preference,
UserId = preference.UserId,
Value = preference.Value,
LegalReference = preference.LegalReference
};
await _memberPreferenceRepository.SaveMemberPreferenceAsync(memberPreference);
settings.Apply(memberPreference);
await _unitOfWork.CompleteAsync(operatorId, dateAdded, userType);
}
return await _unifiedOptInRepository.GetMemberOptInSettings(userId);
}
public bool IsExternalUpdateNecessary(MemberOptInSettings settings, MemberPreferenceRequest request)
{
switch (request.Preference)
{
case PreferenceType.EStatementsEmail:
return settings.EStatementsEmail != request.Value;
case PreferenceType.EStatements:
return settings.EStatements != request.Value;
case PreferenceType.ENotification:
return settings.ENotifications != request.Value;
case PreferenceType.ENotificationsEmail:
return settings.ENotificationsEmail != request.Value;
case PreferenceType.UnifiedOptInEmail:
return settings.UnifiedOptInEmail != request.Value && settings.HasAnyUnifiedPreference();
case PreferenceType.UnifiedOptIn:
return settings.UniversalOptIn != request.Value && settings.HasAnyUnifiedPreference();
default:
return false;
}
}
public async Task UpdateAgreementDeliveryMethodCode(string userId, string deliveryMethod)
{
try
{
var agreement = await _coreApiWrapperService.GetAgreementByUserId(userId);
if (agreement == null)
{
await _notificationService.SendDNAAgreementErrorNotification(userId, deliveryMethod);
return;
}
if (agreement.ByOrgNbr != 0)
{
await UpdateAgreementAccountsForOrganization(userId, deliveryMethod, agreement);
}
else
{
await UpdateAgreementAccountsForPerson(userId, deliveryMethod, agreement);
}
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to Update Agreement Accounts for userId: {userId} to DeliveryMethod: {deliveryMethod}.");
throw;
}
}
private async Task UpdateAgreementAccountsForPerson(string userId, string deliveryMethod, Agreement agreement)
{
try
{
var agreementAccounts = await _coreApiWrapperService.GetAgreementAccountsByUserId(agreement.UserId);
if (!agreementAccounts.Any()) return;
_accountNumbers = agreementAccounts.Where(account => account.TaxReportForPersonNumber != null && account.AccountOwner.AgreementNumber == agreement.AgreementNumber).Select(account => account.AccountNumber).ToList();
if (_accountNumbers.Any()) await _coreApiWrapperService.UpdateAgreementAccountsDeliveryMethod(_accountNumbers, deliveryMethod, userId);
}
catch (Exception ex)
{
var accountsCommaSeperated = string.Join(", ", _accountNumbers.ToArray());
await _notificationService.SendDNAUpdateErrorNotification(userId, accountsCommaSeperated, deliveryMethod);
var error = $"Failed to UpdateAgreementAccountsDeliveryMethod for accounts: {accountsCommaSeperated} {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
private async Task UpdateAgreementAccountsForOrganization(string userId, string deliveryMethod, Agreement agreement)
{
try
{
var agreementAccounts = await _coreApiWrapperService.GetAgreementAccountsByOrganizationId(agreement.ByOrgNbr);
if (!agreementAccounts.Any()) return;
_accountNumbers = agreementAccounts.Where(account => account.TaxReportForOrgNumber != null && account.AccountOwner.AgreementNumber == agreement.AgreementNumber).Select(account => account.AccountNumber).ToList();
if (_accountNumbers.Any()) await _coreApiWrapperService.UpdateAgreementAccountsDeliveryMethod(_accountNumbers, deliveryMethod, userId);
}
catch (Exception ex)
{
var accountsCommaSeperated = string.Join(", ", _accountNumbers.ToArray());
await _notificationService.SendDNAUpdateErrorNotification(userId, accountsCommaSeperated, deliveryMethod);
var error = $"Failed to UpdateAgreementAccountsDeliveryMethod for accounts: {accountsCommaSeperated} {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<MemberPreference>> GetMemberPreferencesAsync(string userId)
{
var preferences = await _memberPreferenceRepository.GetMemberPreferencesAsync(userId);
return preferences ?? new List<MemberPreference>();
}
public IReadOnlyCollection<string> GetAvailableTypes()
{
return Enum.GetValues(typeof(PreferenceType))
.Cast<PreferenceType>()
.Select(v => v.ToString())
.ToList();
}
public async Task UpdateMemberOptinAsync(MemberOptinRequest optinRequest)
{
if (await _xdiBatchItemService.DoesMemberHavePendingXdiChange(optinRequest.UserId))
{
throw new Exception();
}
var oldOptInSettings = await _unifiedOptInRepository.GetMemberOptInSettingsNoTracking(optinRequest.UserId);
var requests = new List<MemberPreferenceRequest>();
if (string.Equals(optinRequest.Value, DeliveryPreference.Electronic, StringComparison.InvariantCultureIgnoreCase))
{
var unused = new System.Net.Mail.MailAddress(optinRequest.Email);
requests.Add(
new MemberPreferenceRequest()
{
Preference = PreferenceType.UnifiedOptInEmail,
Value = optinRequest.Email,
UserId = optinRequest.UserId,
LegalReference = ""
});
}
requests.Add(
new MemberPreferenceRequest()
{
Preference = PreferenceType.UnifiedOptIn,
Value = optinRequest.Value,
UserId = optinRequest.UserId,
LegalReference = optinRequest.LegalReference
});
var preferences = await SaveMemberPreferencesAsync(requests);
await SendOptInChangeNotifications(optinRequest, oldOptInSettings);
await _integrationService.NotifyListeners(preferences);
}
public async Task SendOptInChangeNotifications(MemberOptinRequest request, MemberOptInSettings originalSettings)
{
var affectedEmails = new List<string>();
TryAddHistoricEmails(originalSettings, affectedEmails);
//Unknown -> Paper
if (originalSettings == null && request.IsPaperRequest())
{
return;
}
//NotEmail -> Email
if ((originalSettings == null || !originalSettings.IsUnifiedElectronicEnrolled()) &&
request.IsElectronicRequest())
{
affectedEmails.Add(request.Email);
await _notificationService.SendOptInNotifications(affectedEmails, request);
return;
}
if (originalSettings == null) return;
//Historic -> Paper
if (!originalSettings.HasAnyUnifiedPreference() && request.IsPaperRequest())
{
await _notificationService.SendOptOutNotifications(affectedEmails);
return;
}
//Email -> Paper
if (originalSettings.IsUnifiedElectronicEnrolled() && request.IsPaperRequest())
{
affectedEmails.Add(originalSettings.UnifiedOptInEmail);
await _notificationService.SendOptOutNotifications(affectedEmails);
return;
}
//Email -> Email
if (originalSettings.IsUnifiedElectronicEnrolled() && request.IsElectronicRequest())
{
if (!string.Equals(originalSettings.UnifiedOptInEmail, request.Email,
StringComparison.InvariantCultureIgnoreCase))
{
affectedEmails.Add(originalSettings.UnifiedOptInEmail);
affectedEmails.Add(request.Email);
await _notificationService.SendEmailChangeNotifications(affectedEmails, request);
}
}
}
public void TryAddHistoricEmails(MemberOptInSettings originalSettings, List<string> affectedEmails)
{
if (originalSettings == null || originalSettings.HasAnyUnifiedPreference()) return;
if (!string.IsNullOrWhiteSpace(originalSettings.EStatementsEmail) && originalSettings.IsOldStatementsElectronicEnrolled())
{
affectedEmails.Add(originalSettings.EStatementsEmail.ToLowerInvariant());
}
if (!string.IsNullOrWhiteSpace(originalSettings.ENotificationsEmail) && originalSettings.IsOldNoticesElectronicEnrolled())
{
affectedEmails.Add(originalSettings.ENotificationsEmail.ToLowerInvariant());
}
}
}
@@ -0,0 +1,160 @@
using LCU.API.Internal.MemberPreferencesApi.DataAccess;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Agreements;
namespace LCU.API.Internal.MemberPreferencesApi.MicroApiServices
{
public class CoreApiWrapperService : ICoreApiWrapperService
{
private readonly ICoreApiWrapperDao _coreApiWrapperDao;
private readonly INotificationService _notificationService;
public CoreApiWrapperService(ICoreApiWrapperDao coreApiWrapperDao, INotificationService notificationService)
{
_coreApiWrapperDao = coreApiWrapperDao;
_notificationService = notificationService;
}
public async Task<Agreement> GetAgreementByUserId(string userId)
{
try
{
return await _coreApiWrapperDao.GetAgreementByUserId(userId);
}
catch (Exception ex)
{
var error = $"Failed to GetAgreementByUserId. UserId: {userId} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<Agreement> GetAgreementByAgreementId(long agreementId)
{
try
{
return await _coreApiWrapperDao.GetAgreementByAgreementId(agreementId);
}
catch (Exception ex)
{
var error = $"Failed to GetAgreementByAgreementId. AgreementId: {agreementId} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IEnumerable<Agreement>> GetAgreementsByAccountId(long accountId)
{
try
{
return await _coreApiWrapperDao.GetAgreementsByAccountId(accountId);
}
catch (Exception ex)
{
var error = $"Failed to GetAgreementsByAccountId. AccountId: {accountId} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<AgreementAccount> GetAgreementAccountByAccountId(long accountId)
{
try
{
return await _coreApiWrapperDao.GetAgreementAccountByAccountId(accountId);
}
catch (Exception ex)
{
var error = $"Failed to GetAgreementAccountByAccountId. AccountId: {accountId} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByUserId(string userId)
{
try
{
return await _coreApiWrapperDao.GetAgreementAccountsByUserId(userId);
}
catch (Exception ex)
{
var error = $"Failed to GetAgreementAccountsByUserId. UserId: {userId} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByOrganizationId(long organizationId)
{
try
{
return await _coreApiWrapperDao.GetAgreementAccountsByOrganizationId(organizationId);
}
catch (Exception ex)
{
var error = $"Failed to GetAgreementAccountsByOrganizationId. OrganizationId: {organizationId} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IEnumerable<long>> GetAuditAccountsFromDateLastMaintained(DateTime dateTime)
{
try
{
return await _coreApiWrapperDao.GetAuditAccountsFromDateLastMaintained(dateTime);
}
catch (Exception ex)
{
var error = $"Failed to GetAuditAccountsFromDateLastMaintained. DateTime: {dateTime} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IEnumerable<long>> GetAuditAgreementFromDateLastMaintained(DateTime dateTime)
{
try
{
return await _coreApiWrapperDao.GetAuditAgreementFromDateLastMaintained(dateTime);
}
catch (Exception ex)
{
var error = $"Failed to GetAuditAgreementFromDateLastMaintained. DateTime: {dateTime} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IEnumerable<long>> GetAuditOrganizationFromDateLastMaintained(DateTime dateTime)
{
try
{
return await _coreApiWrapperDao.GetAuditOrganizationFromDateLastMaintained(dateTime);
}
catch (Exception ex)
{
var error = $"Failed to GetAuditOrganizationFromDateLastMaintained. DateTime: {dateTime} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task UpdateAgreementAccountsDeliveryMethod(IEnumerable<long> accountIds, string deliveryMethod, string userId)
{
try
{
await _coreApiWrapperDao.UpdateAgreementAccountsDeliveryMethod(accountIds, deliveryMethod, userId);
}
catch (Exception ex)
{
var accountsCommaSeperated = string.Join(", ", accountIds.ToArray());
await _notificationService.SendDNAUpdateErrorNotification(userId, accountsCommaSeperated, deliveryMethod);
var error = $"Failed to GetAuditOrganizationFromDateLastMaintained. AccountIds: {accountsCommaSeperated} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
}
}
@@ -0,0 +1,30 @@
using LCU.Api.Internal.FTPLib.Models;
using LCU.API.Internal.MemberPreferencesApi.DataAccess;
namespace LCU.API.Internal.MemberPreferencesApi.MicroApiServices
{
public class FtpApiService : IFtpApiService
{
private readonly IFtpApiDao _ftpApiDao;
public FtpApiService(IFtpApiDao ftpApiDao)
{
_ftpApiDao = ftpApiDao;
}
public async Task SendXdiFtpRequest(SshFtpRequest request)
{
try
{
await _ftpApiDao.SendFtpAsync(request);
Log.Information($"XDI Ftp Sent: {request.RemoteFilePath}");
}
catch (Exception ex)
{
var error = $"Failed to send FTP file. Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
}
}
@@ -0,0 +1,17 @@
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Agreements;
namespace LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
public interface ICoreApiWrapperService
{
public Task<Agreement> GetAgreementByUserId(string userId);
public Task<Agreement> GetAgreementByAgreementId(long agreementId);
public Task<IEnumerable<Agreement>> GetAgreementsByAccountId(long accountId);
public Task<AgreementAccount> GetAgreementAccountByAccountId(long accountId);
public Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByUserId(string userId);
public Task<IEnumerable<AgreementAccount>> GetAgreementAccountsByOrganizationId(long organizationId);
public Task<IEnumerable<long>> GetAuditAccountsFromDateLastMaintained(DateTime dateTime);
public Task<IEnumerable<long>> GetAuditAgreementFromDateLastMaintained(DateTime dateTime);
public Task<IEnumerable<long>> GetAuditOrganizationFromDateLastMaintained(DateTime dateTime);
public Task UpdateAgreementAccountsDeliveryMethod(IEnumerable<long> accountIds, string deliveryMethod, string userId);
}
@@ -0,0 +1,8 @@
using LCU.Api.Internal.FTPLib.Models;
namespace LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
public interface IFtpApiService
{
public Task SendXdiFtpRequest(SshFtpRequest request);
}
@@ -0,0 +1,10 @@
namespace LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
public interface INotificationService
{
Task SendOptInNotifications(List<string> recipient, MemberOptinRequest request);
Task SendEmailChangeNotifications(List<string> recipient, MemberOptinRequest request);
Task SendOptOutNotifications(List<string> recipient);
Task SendDNAUpdateErrorNotification(string userId, string accounts, string deliveryMethod);
Task SendDNAAgreementErrorNotification(string userId, string deliveryMethod);
}
@@ -0,0 +1,87 @@
using LCU.API.Internal.MemberPreferencesApi.DataAccess;
namespace LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
public class NotificationService : INotificationService
{
private readonly NotificationApiConfiguration _notificationsApiConfiguration;
private readonly INotificationApiDao _notificationServiceApiDao;
public NotificationService(NotificationApiConfiguration notificationsApiConfiguration, INotificationApiDao notificationApiDao)
{
_notificationsApiConfiguration = notificationsApiConfiguration;
_notificationServiceApiDao = notificationApiDao;
}
private async Task SendMessageAsync(List<string> recipient, string subject, string body)
{
if (recipient == null || !recipient.Any())
{
Log.Information($"A message with subject: {subject} can't be sent to the Notification Api, as no recipients are present.");
return;
}
if (!_notificationsApiConfiguration.ShouldSendRealEmails)
{
recipient = _notificationsApiConfiguration.TestEmailList;
}
var emailRequest = new EmailRequest
{
Recipient = recipient,
Subject = subject,
Body = body
};
try
{
await _notificationServiceApiDao.SendEmailAsync(emailRequest);
}
catch (Exception ex)
{
Log.Error(ex, "An exception was caught trying to send an email via Notifications API.");
}
}
public async Task SendOptInNotifications(List<string> recipients, MemberOptinRequest request)
{
var filteredRecipients = recipients.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
var subject = "Welcome To E-Statements!";
var message =
$"<div style=\"padding: 2em;\"><p style=\"color: #0054a4;\"><strong>You have enrolled to receive electronic notifications for your statements and notices. You'll love the convenience!</strong></p><p>You will be receiving emails to: {request.Email}</p><p>With electronic statements through Landmark Credit Union Digital Banking, you&rsquo;ll be able to:</p><ul><li>View your statements and tax documents online or on your mobile device</li><li>Maintain your own secure online archive</li><li>Help the environment by reducing paper and clutter</li></ul><p>You&rsquo;ll receive an email whenever a new statement becomes available. If you need to make changes to any of your preferences, you can visit Manage Delivery Preferences under Statements &amp; Notices.</p><div style=\"color: #959ba6;\"><center><p style=\"margin:0px;\">You are receiving this email because you enabled e-statements in Landmark Credit Union. If you did not make this selection or believe this is in error, please contact us.</p><p style=\"margin:0px;\"><a href=\"http://www.landmarkcu.com/\">Landmark Credit Union</a> | <a href=\"https://landmarkcu.com/contact-us/\">Contact Us</a></p><p style=\"margin:0px;\">Landmark Credit Union</p><p style=\"margin:0px;\">&copy; 2021 All rights reserved</p></center></div></div>";
await SendMessageAsync(filteredRecipients, subject, message);
}
public async Task SendEmailChangeNotifications(List<string> recipients, MemberOptinRequest request)
{
var filteredRecipients = recipients.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
var subject = "E-Statements Email Change";
var message =
$"<div style=\"padding: 2em;\"><p style=\"color: #0054a4;\"><strong>You have changed your preferred email address to receive statement and notice notifications.</strong></p><p>You will now receive emails to: {request.Email}</p><p>Your security is important to us and we wanted to confirm that you recently changed the email address you prefer to use for receiving statement and notice notifications. If this email is incorrect, you can update it in your Landmark Credit Union Digital Banking account by visiting Manage Delivery Preferences under Statements &amp; Notices. If you did not make this change, please contact us.</p><div style=\"color: #959ba6;\"><center><p style=\"margin: 0px;\">You are receiving this email because you updated the email address you are using to receive statement notifications. If you did not make this selection or believe this is in error, please contact us.</p><p style=\"margin: 0px;\"><a href=\"http://www.landmarkcu.com/\">Landmark Credit Union</a> | <a href=\"https://landmarkcu.com/contact-us/\">Contact Us</a></p><p style=\"margin: 0px;\">Landmark Credit Union</p><p style=\"margin: 0px;\">&copy; 2021 All rights reserved</p></center></div></div>";
await SendMessageAsync(filteredRecipients, subject, message);
}
public async Task SendOptOutNotifications(List<string> recipients)
{
var filteredRecipients = recipients.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
var subject = "Paper Statement Enrollment";
var message =
$"<div style=\"padding: 2em;\"><p style=\"color: #0054a4;\"><strong>You have enrolled to receive paper for your statements and notices.</strong></p><p>You have selected to receive paper statements and notices. You will no longer be able to view these documents online or on your mobile device.</p><p>With electronic statements through Landmark Credit Union Digital Banking, you&rsquo;ll be able to:</p><ul><li>View your statements and tax documents online or on your mobile device</li><li>Maintain your own secure online archive</li><li>Help the environment by reducing paper and clutter</li></ul><p>Please note, some accounts may have promotional rates or offers that require paperless statements and electing to receive paper may impact offers on your account. Please refer to your account Truth in Savings disclosure for more information. If you need to make changes to any of your preferences, you can visit Manage Delivery Preferences under Statements &amp; Notices.</p><div style=\"color: #959ba6;\"><center><p style=\"margin:0px;\">You are receiving this email because you requested to receive paper statements and notices. If you did not make this selection or believe this is in error, please contact us.</p><p style=\"margin:0px;\"><a href=\"http://www.landmarkcu.com/\">Landmark Credit Union</a> | <a href=\"https://landmarkcu.com/contact-us/\">Contact Us</a></p><p style=\"margin:0px;\">Landmark Credit Union</p><p style=\"margin:0px;\">&copy; 2021 All rights reserved</p></center></div></div>";
await SendMessageAsync(filteredRecipients, subject, message);
}
public async Task SendDNAUpdateErrorNotification(string userId, string accounts, string deliveryMethod)
{
const string subject = "ERROR: Unified Opt In Tool Encountered an Error updating DNA";
var message =
$"<br/><br/><h3>Unified Opt In Tool Encountered an Error updating DNA</h3><hr/> Unified Opt In Tool was unable to sync a member’s selected preference with DNA due to an unexpected error. Please investigate and manually update the following account(s) delivery method in DNA if the change is valid.<br><br><b>Member’s eAgreement User Id:</b> {userId}<br><b>Affected accounts owned by person:</b> {accounts}<br><b>Attempting to update to:</b> {deliveryMethod}";
await SendMessageAsync(_notificationsApiConfiguration.DNACoreErrorEmailList, subject, message);
}
public async Task SendDNAAgreementErrorNotification(string userId, string deliveryMethod)
{
const string subject = "ERROR: Unified Opt In Tool Encountered an Error finding Agreement in DNA";
var message = $"<br/><br/><h3>Unified Opt In Tool Encountered an Error finding Agreement in DNA</h3><hr/> Unified Opt In Tool was unable to sync a member’s selected preference with DNA due to an unexpected error. Please investigate and manually update the accounts associated delivery method in DNA if the change is valid.<br><br><b>Member’s eAgreement User Id:</b> {userId}<br><b>Attempting to update to:</b> {deliveryMethod}";
await SendMessageAsync(_notificationsApiConfiguration.DNACoreErrorEmailList, subject, message);
}
}
@@ -0,0 +1,125 @@
using LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
using LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
using LCU.API.Internal.MemberPreferencesApiLib.Repositories;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Agreements;
using LCU.Library.Internal.MemberPreferencesApiModels.DataTransfer;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public class OptinService : IOptinService
{
private readonly IUnifiedOptInRepository _unifiedOptInRepository;
private readonly IXdiBatchService _xdiBatchItemService;
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IMemberPreferenceService _memberPreferenceService;
public OptinService(IUnifiedOptInRepository unifiedOptInRepository, IXdiBatchService xdiBatchItemService, IMemberPreferenceService memberPreferenceService,
ICoreApiWrapperService coreApiWrapperService)
{
_unifiedOptInRepository = unifiedOptInRepository;
_xdiBatchItemService = xdiBatchItemService;
_memberPreferenceService = memberPreferenceService;
_coreApiWrapperService = coreApiWrapperService;
}
public async Task<MemberOptInSettings> GetMemberOptInSettings(string userId)
{
return await _unifiedOptInRepository.GetMemberOptInSettings(userId);
}
public async Task<MemberOptInSettings> GetMemberOptInSettingsOrCreateDefaultSetting(string userId)
{
var memberOptInSettings = await _unifiedOptInRepository.GetMemberOptInSettings(userId);
try
{
if (memberOptInSettings == null)
{
var memberPreferenceRequest = new MemberPreferenceRequest()
{
UserId = userId,
Preference = PreferenceType.UnifiedOptIn,
LegalReference = DateTime.Now.ToString("s"),
Value = "Paper"
};
memberOptInSettings = await _memberPreferenceService.SaveMemberPreferenceDefaultAsync(memberPreferenceRequest, "System", UserType.System, DateTime.UtcNow);
}
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to create new MemberOptInSettings with default of paper for UserId: {userId}");
}
return memberOptInSettings;
}
public async Task<IReadOnlyCollection<MemberOptInAgreementInformation>> GetMemberOptInAgreementInformationByAccountId(long accountId)
{
var response = new List<MemberOptInAgreementInformation>();
var agreements = await _coreApiWrapperService.GetAgreementsByAccountId(accountId);
if (agreements == null || !agreements.Any()) return response;
foreach (var agreement in agreements)
{
var accounts = await GetAgreementAccountsForAgreement(agreement);
var memberOptinSettings = await _unifiedOptInRepository.GetMemberOptInSettings(agreement.UserId);
var isPending = await _xdiBatchItemService.DoesMemberHavePendingXdiChange(agreement.UserId);
response.Add(new MemberOptInAgreementInformation(memberOptinSettings, agreement, accounts, isPending));
}
return response;
}
private async Task<IEnumerable<AgreementAccount>> GetAgreementAccountsForAgreement(Agreement agreement)
{
IEnumerable<AgreementAccount> accounts;
if (agreement.ByOrgNbr != 0)
{
accounts = await _coreApiWrapperService.GetAgreementAccountsByOrganizationId(agreement.ByOrgNbr);
}
else
{
accounts = await _coreApiWrapperService.GetAgreementAccountsByUserId(agreement.UserId);
}
return accounts;
}
public async Task<MemberOptInAgreementInformation> GetMemberOptInAgreementInformationForUser(string userId)
{
var agreement = await _coreApiWrapperService.GetAgreementByUserId(userId);
var memberOptinSettings = await GetMemberOptInSettingsOrCreateDefaultSetting(userId);
var isPending = await _xdiBatchItemService.DoesMemberHavePendingXdiChange(userId);
if (agreement == null)
{
return new MemberOptInAgreementInformation(memberOptinSettings, null, null, isPending);
}
var accounts = await GetAgreementAccountsForAgreement(agreement);
return new MemberOptInAgreementInformation(memberOptinSettings, agreement, accounts, isPending);
}
public async Task<IReadOnlyCollection<MemberOptInInformation>> TransformMemberOptInSettingsForClient(List<MemberOptInSettings> memberOptInSettingsList)
{
var response = new List<MemberOptInInformation>();
foreach (var setting in memberOptInSettingsList)
{
var isPending = await _xdiBatchItemService.DoesMemberHavePendingXdiChange(setting.UserId);
response.Add(new MemberOptInInformation(setting, isPending));
}
return response;
}
public async Task<MemberOptInInformation> TransformMemberOptInSettingsForClient(MemberOptInSettings memberOptInSettings)
{
var results = await TransformMemberOptInSettingsForClient(new List<MemberOptInSettings> { memberOptInSettings });
return results.Single();
}
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
public interface IQueueDnaAccountAuditService
{
Task QueueDataAuditForAccounts(List<long> accountNumbers, int consecutiveFailures = 0);
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
public interface IQueueDnaAgreementAuditService
{
Task QueueDataAuditForAgreements(List<long> agreementAuditRecords, int consecutiveFailures = 0);
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
public interface IQueueDnaOrganizationAuditService
{
Task QueueDataAuditForOrganizations(List<long> organizationNumbers, int consecutiveFailures = 0);
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
public interface IQueueXdiBatchService
{
Task QueueXdiBatchProcess(XdiBatchRequest request);
}
@@ -0,0 +1,44 @@
using LCU.Library.Internal.LcuMassTransitModels;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices
{
public class QueueDnaAccountAuditService : IQueueDnaAccountAuditService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfiguration _dnaAccountAuditReceiveEndpoint;
public QueueDnaAccountAuditService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations)
{
_dnaAccountAuditReceiveEndpoint = receiveEndpointConfigurations.DnaAccountAuditEndpoint;
_sendEndpoint = busControl.GetSendEndpoint(_dnaAccountAuditReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueDataAuditForAccounts(List<long> accountAuditRecords, int consecutiveFailures = 0)
{
try
{
foreach (var account in accountAuditRecords)
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var request = new DnaFieldChangeRequest()
{
DnaFieldIdInformation = account,
ConsecutiveFailures = consecutiveFailures,
DnaProcessType = DnaAuditProcessType.DNAAccounts,
LastUpdated = DateTime.UtcNow,
ProcessId = 2
};
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
};
}
catch (Exception ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(ex, "Failed to Queue MemberPreference DNA Account Audit records.");
throw;
}
}
}
}
@@ -0,0 +1,43 @@
using LCU.Library.Internal.LcuMassTransitModels;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
public class QueueDnaAgreementAuditService : IQueueDnaAgreementAuditService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfiguration _dnaAuditAgreementReceiveEndpoint;
public QueueDnaAgreementAuditService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations)
{
_dnaAuditAgreementReceiveEndpoint = receiveEndpointConfigurations.DnaAgreementAuditEndpoint;
_sendEndpoint = busControl.GetSendEndpoint(_dnaAuditAgreementReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueDataAuditForAgreements(List<long> agreementAuditRecords, int consecutiveFailures = 0)
{
try
{
foreach (var agreement in agreementAuditRecords)
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var request = new DnaFieldChangeRequest()
{
DnaFieldIdInformation = agreement,
ConsecutiveFailures = consecutiveFailures,
DnaProcessType = DnaAuditProcessType.DNAAgreement,
LastUpdated = DateTime.UtcNow,
ProcessId = 1
};
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
}
catch (Exception ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(ex, "Failed to Queue MemberPreference DNA Account Audit records.");
throw;
}
}
}
@@ -0,0 +1,44 @@
using LCU.Library.Internal.LcuMassTransitModels;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
public class QueueDnaOrganizationService : IQueueDnaOrganizationAuditService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfiguration _dnaAuditOrganizationReceiveEndpoint;
public QueueDnaOrganizationService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations)
{
_dnaAuditOrganizationReceiveEndpoint = receiveEndpointConfigurations.DnaOrganizationAuditEndpoint;
_sendEndpoint = busControl.GetSendEndpoint(_dnaAuditOrganizationReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueDataAuditForOrganizations(List<long> organizationAuditRecords, int consecutiveFailures = 0)
{
try
{
foreach (var organization in organizationAuditRecords)
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var request = new DnaFieldChangeRequest()
{
DnaFieldIdInformation = organization,
ConsecutiveFailures = consecutiveFailures,
DnaProcessType = DnaAuditProcessType.DNAOrganization,
LastUpdated = DateTime.UtcNow,
ProcessId = 3
};
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
}
catch (Exception ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(ex, "Failed to Queue MemberPreference DNA Account Audit records.");
throw;
}
}
}
@@ -0,0 +1,32 @@
using LCU.Library.Internal.LcuMassTransitModels;
using MassTransit;
namespace LCU.API.Internal.MemberPreferencesApi.Services.QueueServices
{
public class QueueXdiBatchService : IQueueXdiBatchService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfiguration _xdiServiceEndpoint;
public QueueXdiBatchService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations)
{
_xdiServiceEndpoint = receiveEndpointConfigurations.XdiBatchProcessEndpoint;
_sendEndpoint = busControl.GetSendEndpoint(_xdiServiceEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueXdiBatchProcess(XdiBatchRequest request)
{
try
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
catch (Exception ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(ex, "Failed to Queue XdiBatchProcess.");
throw;
}
}
}
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services;
[AttributeUsage(AttributeTargets.Method)]
public class ReadsCurrentUserAttribute : Attribute
{
}
@@ -0,0 +1,42 @@
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection;
namespace LCU.API.Internal.MemberPreferencesApi.Services;
public class ReadsCurrentUserOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.MethodInfo.GetCustomAttribute<ReadsCurrentUserAttribute>() is null) return;
operation.Parameters ??= new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "UserName",
In = ParameterLocation.Header,
Required = true,
Schema = new OpenApiSchema
{
Type = "string"
}
});
operation.Parameters.Add(new OpenApiParameter
{
Name = "UserType",
In = ParameterLocation.Header,
Required = true,
Schema = new OpenApiSchema
{
Enum = Enum.GetValues(typeof(UserType))
.Cast<UserType>()
.Select(t => new OpenApiString(t.ToString()))
.ToList<IOpenApiAny>()
}
});
}
}
@@ -0,0 +1,6 @@
namespace LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
public interface IXdiBatchProcessService
{
Task RunXdiBatchProcess(string sftpRootBasePath);
}
@@ -0,0 +1,17 @@
using LCU.API.Internal.MemberPreferencesApi.Models;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using ApiResponse = LCU.Library.Internal.ApiResponse.Models;
namespace LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
public interface IXdiBatchService
{
Task AddXdiBatchItem(XdiBatchItem newItem);
Task CreateXdiBatchFiles(XdiBatchRequest request);
Task<XdiBatchRequest> BuildXdiBatchRequest(XdiBatchRequest request);
Task HandleFtpProcess(XdiBatchRequest request);
Task<bool> RequestFtpTransport(IReadOnlyCollection<XdiBatchFile> files);
Task<bool> MarkPendingSuccessful(ApiResponse.Response response);
Task<List<FtpQueuePayloadItem>> CreateFtpPayload(XdiBatchFile xdiBatchFile);
Task<bool> DoesMemberHavePendingXdiChange(string memberId);
}
@@ -0,0 +1,49 @@
using LCU.API.Internal.MemberPreferencesApi.Models;
using LCU.API.Internal.MemberPreferencesApi.Services.QueueServices;
using LCU.API.Internal.MemberPreferencesApiLib.Repositories;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
namespace LCU.API.Internal.MemberPreferencesApi.Services.XdiServices;
public class XdiBatchProcessService : IXdiBatchProcessService
{
private readonly IXdiBatchItemsRepository _xdiBatchItemsRepository;
private readonly IQueueXdiBatchService _queueXdiBatchService;
public XdiBatchProcessService(IQueueXdiBatchService queueXdiBatchService, IXdiBatchItemsRepository xdiBatchItemsRepository)
{
_xdiBatchItemsRepository = xdiBatchItemsRepository;
_queueXdiBatchService = queueXdiBatchService;
}
public async Task RunXdiBatchProcess(string sftpRootBasePath)
{
try
{
XdiBatch xdiBatch = new XdiBatch();
var xdiBatchItems = await _xdiBatchItemsRepository.GetXdiBatchItems();
if (xdiBatchItems != null || !xdiBatchItems.Any())
{
Log.Information($"Creating XDI Batch Files. Out of {xdiBatchItems.Count()} total items {xdiBatchItems.Count(x => x.BatchState == BatchState.TransferPending)} " +
$"are reprocessed items that did not receive a success response last run.");
var request = new XdiBatchRequest()
{
XdiBatch = xdiBatch,
XdiBatchItems = xdiBatchItems.ToList(),
SftpRootBasePath = sftpRootBasePath
};
await _queueXdiBatchService.QueueXdiBatchProcess(request);
}
}
catch (Exception ex)
{
var error = $"Failed to Run XdiBatchProcess. Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
}
@@ -0,0 +1,273 @@
using ApiResponse = LCU.Library.Internal.ApiResponse.Models;
using LCU.API.Internal.MemberPreferencesApi.MicroApiServices;
using LCU.API.Internal.MemberPreferencesApiLib.Repositories;
using LCU.Api.Internal.FTPLib.Models;
using LCU.Library.Internal.ApiResponse.Models;
using LCU.Library.Internal.LcuMassTransitModels;
using LCU.Library.Internal.MemberPreferencesApiModels.Entities;
using LCU.Library.Internal.MemberPreferencesApiModels.Enums;
using LCU.API.Internal.MemberPreferencesApi.Models;
using Newtonsoft.Json;
namespace LCU.API.Internal.MemberPreferencesApi.Services.XdiServices
{
public class XdiBatchService : IXdiBatchService
{
private readonly IXdiBatchItemsRepository _xdiBatchItemsRepository;
private readonly IUnifiedOptInRepository _unifiedOptInRepository;
private readonly IMemberPreferenceRepository _memberPreferenceRepository;
private readonly ConnectionConfiguration _connectionConfiguration;
private readonly ReceiveEndpointConfiguration _completionReceiveEndpoint;
private readonly IUnitOfWork _unitOfWork;
private readonly XdiSftpApiConfiguration _xdiSftpApiConfiguration;
private readonly IFtpApiService _ftpApiService;
private const string XdiBatchProcessOperatorName = "XdiBatchProcess";
public XdiBatchService(IXdiBatchItemsRepository xdiBatchItemsRepository, IUnitOfWork unitOfWork,
XdiSftpApiConfiguration xdiSftpApiConfiguration, IUnifiedOptInRepository unifiedOptInRepository,
IMemberPreferenceRepository memberPreferenceRepository, ConnectionConfiguration connectionConfiguration,
ReceiveEndpointConfigurations receiveEndpointsConfigurations, IFtpApiService ftpApiService)
{
_xdiBatchItemsRepository = xdiBatchItemsRepository;
_unitOfWork = unitOfWork;
_xdiSftpApiConfiguration = xdiSftpApiConfiguration;
_unifiedOptInRepository = unifiedOptInRepository;
_memberPreferenceRepository = memberPreferenceRepository;
_connectionConfiguration = connectionConfiguration;
_ftpApiService = ftpApiService;
_completionReceiveEndpoint = receiveEndpointsConfigurations.XdiBatchConfirmEndpoint;
}
public async Task CreateXdiBatchFiles(XdiBatchRequest request)
{
try
{
if (!request.XdiBatchItems.Any())
{
return;
}
request = await BuildXdiBatchRequest(request);
await HandleFtpProcess(request);
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to Create XDI Batch Files.");
}
}
public async Task<XdiBatchRequest> BuildXdiBatchRequest(XdiBatchRequest request)
{
foreach (var item in request.XdiBatchItems)
{
var currentSettings = await _unifiedOptInRepository.GetMemberOptInSettings(item.UserId);
if (currentSettings != null)
{
var result = request.XdiBatch.ProcessItem(item, currentSettings);
if (result == true)
{
_xdiBatchItemsRepository.Delete(item);
await _xdiBatchItemsRepository.SaveAsync(new XdiBatchItem()
{
UserId = item.UserId,
Created = item.Created,
BatchState = BatchState.TransferPending,
LastUpdated = DateTime.UtcNow
});
Log.Information($"Hosted XDI Sync processed batch item with UserId: {item.UserId}");
}
else
{
Log.Error($"Row not processed in XDI Audit for member with UserId: {item.UserId}, marking complete.");
_xdiBatchItemsRepository.Delete(item);
}
};
};
await _unitOfWork.CompleteAsync(XdiBatchProcessOperatorName, DateTime.UtcNow, UserType.System);
return request;
}
public async Task HandleFtpProcess(XdiBatchRequest request)
{
try
{
IReadOnlyCollection<XdiBatchFile> batchFiles = request.XdiBatch.GetNonEmptyFiles(request.SftpRootBasePath);
Log.Information($"Hosted XDI Sync produced {batchFiles.Count} files");
bool results = await RequestFtpTransport(batchFiles);
string resultDescription = results ? "Success" : "Failure";
Log.Information($"Hosted XDI Sync Ftp Results...{resultDescription}");
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to Handle FTP Process.");
}
}
public async Task<bool> RequestFtpTransport(IReadOnlyCollection<XdiBatchFile> files)
{
SshFtpRequest request = BuildFtpRequest();
foreach (var xdiBatchFile in files)
{
request.LocalFileData = xdiBatchFile.ConvertToDataFile();
request.RemoteFilePath = xdiBatchFile.GetFileName();
var payload = await CreateFtpPayload(xdiBatchFile);
request.ResponseItems.First().ResponsePayload = JsonConvert.SerializeObject(payload).ToString();
await _ftpApiService.SendXdiFtpRequest(request);
}
return true;
}
private SshFtpRequest BuildFtpRequest()
{
var request = new SshFtpRequest()
{
ResponseItems = new List<ResponseItem>()
{
new ResponseItem()
{
ResponsePayload = "",
ReturnQueueConnectionConfig = new QueueConnectionConfig()
{
Url = _connectionConfiguration.HostUrl,
Username = _connectionConfiguration.UserName,
Password = _connectionConfiguration.Password
},
ReturnQueueConsumerConfig = new QueueConsumerConfig()
{
ExchangeName = _completionReceiveEndpoint.ReceiveEndpoint
}
}
},
HostName = _xdiSftpApiConfiguration.HostName,
Password = _xdiSftpApiConfiguration.Password,
PortNumber = _xdiSftpApiConfiguration.PortNumber,
UserName = _xdiSftpApiConfiguration.UserName
};
return request;
}
public async Task<List<FtpQueuePayloadItem>> CreateFtpPayload(XdiBatchFile xdiBatchFile)
{
List<FtpQueuePayloadItem> payload = new List<FtpQueuePayloadItem>();
var userIds = xdiBatchFile.Data.Select(x => x.UserId).ToList();
foreach (var userId in userIds)
{
var settings = await _unifiedOptInRepository.GetMemberOptInSettingsNoTracking(userId);
if (settings != null)
{
payload.Add(new FtpQueuePayloadItem
{
UserId = userId,
Value = settings.UniversalOptIn == DeliveryPreference.Electronic
? settings.UnifiedOptInEmail
: DeliveryPreference.Paper
});
}
}
return payload;
}
public async Task AddXdiBatchItem(XdiBatchItem newItem)
{
var existing = await _xdiBatchItemsRepository.FindExistingItem(newItem.UserId, BatchState.New);
if (existing != null)
{
existing.LastUpdated = newItem.Created;
_xdiBatchItemsRepository.Update(existing);
}
else
{
await _xdiBatchItemsRepository.SaveAsync(newItem);
}
await _unitOfWork.CompleteAsync();
}
public async Task<bool> DoesMemberHavePendingXdiChange(string userId)
{
return await _xdiBatchItemsRepository.FindExistingItem(userId, BatchState.TransferPending) != null;
}
public async Task<bool> MarkPendingSuccessful(ApiResponse.Response response)
{
try
{
var payloadString = response.ResponsePayload;
var payload = JsonConvert.DeserializeObject<List<FtpQueuePayloadItem>>(payloadString);
if (!response.Successful)
{
var errorMessage = string.Join("; ", response.ResponseStack.ToArray());
throw new ServiceException($"XdiBatchItemService Failure occured. Payload response was unsuccessful. Error Message: {errorMessage}");
}
Log.Information($"Marking {payload.Count} Payloads Successful.");
var memberIds = payload.Select(x => x.UserId);
var result = await _xdiBatchItemsRepository.MarkPendingSuccessful(memberIds.ToList());
if (result)
{
await UpdateMemberPreferenceEntries(payload);
}
await _unitOfWork.CompleteAsync(XdiBatchProcessOperatorName, DateTime.UtcNow, UserType.System);
return result;
}
catch (ServiceException serviceEx)
{
Log.Error($"{serviceEx}");
throw serviceEx;
}
catch (Exception ex)
{
Log.Error(ex, $"Unexpected Error attempting to Mark Pending Items Successful.");
throw;
}
}
private async Task UpdateMemberPreferenceEntries(List<FtpQueuePayloadItem> payload)
{
foreach (var item in payload)
{
var settings = await _unifiedOptInRepository.GetOrCreateMemberOptinSettings(item.UserId);
if (item.Value == DeliveryPreference.Paper)
{
var paperPreference = new MemberPreference
{
UserId = item.UserId,
LegalReference = DateTime.Now.ToString("s"),
Preference = PreferenceType.ENotification,
Value = DeliveryPreference.Paper
};
await _memberPreferenceRepository.SaveMemberPreferenceAsync(paperPreference);
settings.Apply(paperPreference);
}
else
{
await _memberPreferenceRepository.BulkSaveMemberPreferenceAsync(new List<MemberPreference>()
{
new MemberPreference()
{
UserId = item.UserId,
LegalReference = DateTime.Now.ToString("s"),
Preference = PreferenceType.ENotification,
Value = DeliveryPreference.Electronic
},
new MemberPreference()
{
UserId = item.UserId,
LegalReference = DateTime.Now.ToString("s"),
Preference = PreferenceType.ENotificationsEmail,
Value = item.Value
}
});
}
}
}
}
}
@@ -0,0 +1,112 @@
{
"Primary": {
"AppName": "Member Preferences Api",
"SeqUrl": "http://localhost:5080",
"SeqKey": "9MQu0vLDwQ1LCFzGixZM",
"XdiBatchProcessCronSchedule": "1 5-18 * * *",
"DnaDataAuditProcessCronSchedule": "*/5 3-19 * * *",
"FeatureFlags": {
"RunHostedServices": true,
"RunXdiBatchProcessConsumer": true,
"RunXdiSshResponseConsumer": true,
"RunAccountDnaFieldChangeConsumer": true,
"RunAgreementDnaFieldChangeConsumer": true,
"RunOrganizationDnaFieldChangeConsumer": true
},
"FeatureGates": {
"EnableControllers": true,
"BounceBackNotifications": false
},
"CorsAllowedOrigins": [
"http://localhost:3000",
"http://lcu-internal.com",
"https://lcu-internal.com",
"https://qa-api.lcu-internal.com"
]
},
"ApiHealthCheckConfiguration": {
"VaultStatusConfiguration": {
"RunVaultStatusCheck": true,
"EnvironmentFromVault": "",
"EnvironmentFromConfigmap": "local"
}
},
"SqlConnectionConfiguration": {
"ConnectionString": "data source=localhost;initial catalog=MemberPreferences;Integrated Security=SSPI;MultipleActiveResultSets=True;App=EntityFramework;Encrypt=False",
"MaxRetryCount": 3,
"MaxRetryDelay": "00:00:30"
},
"NotificationApiConfiguration": {
"Url": "https://localhost:53144/api/",
"Email": "Email",
"DNACoreErrorEmailList": [ "[email protected]" ],
"TestEmailList": [ "[email protected]" ],
"ShouldSendRealEmails": false
},
"FtpApiConfiguration": {
"Url": "https://localhost:56641/",
"QueueSshFtpRequestEndpoint": "SshFtp/QueueSshFtpRequest"
},
"XdiSftpApiConfiguration": {
"SftpRootBasePath": "/home/devops/",
"HostName": "10.147.44.23",
"UserName": "devops",
"PortNumber": 22,
"Password": "vault-value",
"Fingerprint": "ssh-ed25519 255 Yj16sxy1rlbKuNxGnYTNqnDUkPjiV8qFiEAjb6h9Xdk="
},
"CoreApiWrapperConfiguration": {
"CoreApiBaseUrl": "https://localhost:44355/api/core/",
"CoreApiGetAgreementByUserId": "Agree/AgreementByUserId/{userId}",
"CoreApiGetAgreementByAgreementId": "Agree/AgreementByAgreementId/{agreementId}",
"CoreApiGetAgreementsByAccountId": "Agree/AgreementsByAccountId/{accountId}",
"CoreApiGetAgreementAccountByAccountId": "Agree/AgreementAccountByAccountId/{accountId}",
"CoreApiGetAgreementAccountsByUserId": "Agree/AgreementAccountsByUserId/{userId}",
"CoreApiGetAgreementAccountsByOrganizationId": "Agree/AgreementAccountsByOrganizationId/{organizationId}",
"CoreApiGetAuditAccountsFromDateLastMaintained": "Agree/AgreementAccountsAuditFromDateLastMaintained",
"CoreApiGetAuditAgreementFromDateLastMaintained": "Agree/AgreementAuditFromDateLastMaintained",
"CoreApiGetAuditOrganizationFromDateLastMaintained": "Agree/OrganizationAuditFromDateLastMaintained",
"CoreApiUpdateAccountsMaintenanceRequest": "Account/AccountMaintenanceRequest"
},
"XdiBatchProcessEndpoint": {
"ReceiveEndpoint": "LCU.MemberPreferencesApi.XdiBatchProcess",
"ReceiveEndpointType": "exchange",
"PrefetchLimit": 1
},
"XdiBatchConfirmEndpoint": {
"ReceiveEndpoint": "LCU.MemberPreferencesApi.XdiBatchConfirm",
"ReceiveEndpointType": "exchange",
"PrefetchLimit": 1
},
"DnaAccountAuditEndpoint": {
"ReceiveEndpoint": "LCU.MemberPreferencesApi.DnaAccountAudit",
"ReceiveEndpointType": "exchange",
"PrefetchLimit": 1
},
"DnaOrganizationAuditEndpoint": {
"ReceiveEndpoint": "LCU.MemberPreferencesApi.DnaOrganizationAudit",
"ReceiveEndpointType": "exchange",
"PrefetchLimit": 1
},
"DnaAgreementAuditEndpoint": {
"ReceiveEndpoint": "LCU.MemberPreferencesApi.DnaAgreementAudit",
"ReceiveEndpointType": "exchange",
"PrefetchLimit": 1
},
"ConnectionConfiguration": {
"HostUrl": "localhost",
"UserName": "guest",
"Password": "guest"
},
"DnaAuditProcessConfiguration": {
"LookBackMinutes": -5,
"NumberOfAllowedConsecutiveFailures": 12
},
"ApiSecurityConfiguration": {
"ApiKeyAuthenticationFailureBehavior": "Error",
"ExternalApiKeySet": {},
"InternalApiKeySet": {
"MembershipPreferences": "85c3f03b-f412-4f8d-bc1e-438a7a69cbbe"
}
}
}
@@ -0,0 +1,7 @@
global using LCU.Library.Internal.Exceptions;
global using LCU.Library.Internal.MemberPreferencesApiModels.Requests;
global using LCU.Api.Internal.MaidBotLib.Models;
global using LCU.API.Internal.MemberPreferencesApi.Models.Requests;
global using LCU.API.Internal.MemberPreferencesApi.Configuration;
global using Serilog;