first commit
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Domain
|
||||
{
|
||||
public class EmailRequest
|
||||
{
|
||||
public string Recipient { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public string Body { get; set; }
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Landmark.OLA.Models;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Domain
|
||||
{
|
||||
public class StoredSettings : OlaModel
|
||||
{
|
||||
public DateTime LastUpdated { get; set; }
|
||||
[Required]
|
||||
public string LastUpdatedBy { get; set; }
|
||||
public bool ApplicationDisabled { get; set; }
|
||||
public string AppDisabledMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Landmark.OLA.Models;
|
||||
using Landmark.OLA.Models.Addresses.Enums;
|
||||
using Landmark.OLA.Models.Individuals.Enums;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.Loan.Enums;
|
||||
using static Landmark.OLA.Models.Loan.TermLengths;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Domain
|
||||
{
|
||||
public class UiSettings
|
||||
{
|
||||
public string AbaRouting => "275079714";
|
||||
|
||||
public Dictionary<string, string> Contact => new Dictionary<string, string>
|
||||
{
|
||||
{ "HelpPhone", "262-796-4500" },
|
||||
};
|
||||
|
||||
public Dictionary<string, string> Links => new Dictionary<string, string>
|
||||
{
|
||||
{ "PrivacyPolicy", "https://www.example.com/" },
|
||||
{ "MemberSite", "https://www.example.net" },
|
||||
{ "HomePage", "https://www.landmarkcu.com/"},
|
||||
};
|
||||
|
||||
public Dictionary<decimal, List<int>> MfdHomeAndRvByAmount = TermLengths.MfdHomeAndRvByAmount;
|
||||
|
||||
public List<int> HomeEquity = TermLengths.HomeEquity;
|
||||
|
||||
public List<int> Special = TermLengths.Special;
|
||||
|
||||
public List<int> MortgageRefiExpress = TermLengths.MortgageRefiExpress;
|
||||
|
||||
public List<int> VehiclePreApproval = TermLengths.VehiclePreApproval;
|
||||
|
||||
public Dictionary<int, List<int>> TermsByYear = TermLengths.TermsByYear;
|
||||
|
||||
public Dictionary<decimal, List<int>> BoatLoansByAmount = TermLengths.BoatLoansByAmount;
|
||||
|
||||
public Dictionary<decimal, List<int>> PowersportByAmount = TermLengths.PowersportByAmount;
|
||||
|
||||
public Dictionary<decimal, List<int>> SignatureByAmount = TermLengths.SignatureByAmount;
|
||||
|
||||
public Dictionary<LoanType, List<LoanPurpose.Purpose>> TypePurposes = LoanPurpose.TypePurposes;
|
||||
|
||||
public Dictionary<LoanType, Limit> Limits = LoanLimits.Limits;
|
||||
|
||||
public List<StateOption> StatesRequiringSpouseInformation = ValidationConstants.StatesRequiringSpouseInformation;
|
||||
|
||||
public List<StateOption> StateOptions = ((StateOption[])Enum.GetValues(typeof(StateOption))).ToList();
|
||||
|
||||
public Dictionary<Frequency, Limit> IncomeLimits = Models.Individuals.IncomeLimits.Limits;
|
||||
|
||||
public BranchConfig BranchMapping = null; // Provided by MongoDB
|
||||
|
||||
public BranchNameConfig BranchNameMapping = null; // Provided by MongoDB
|
||||
|
||||
public StoredSettings Configuration = null; // Provided by MongoDB
|
||||
|
||||
public List<UiTermOption> TermOptions = AvailableTermOptions;
|
||||
|
||||
public List<UiTermOption> TermOptionsNoYear = AvailableTermOptionsWithoutYears;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public class AzureActiveDirectoryOptions
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
public string Instance { get; set; }
|
||||
public string Domain { get; set; }
|
||||
public string TenantId { get; set; }
|
||||
public string ValidAudience { get; set; }
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public static class AzureAdServiceCollectionExtensions
|
||||
{
|
||||
public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
|
||||
=> builder.AddAzureAdBearer(_ => { });
|
||||
|
||||
public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action<AzureActiveDirectoryOptions> configureOptions)
|
||||
{
|
||||
builder.Services.Configure(configureOptions);
|
||||
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
|
||||
builder.AddJwtBearer();
|
||||
return builder;
|
||||
}
|
||||
|
||||
private class ConfigureAzureOptions : IConfigureNamedOptions<JwtBearerOptions>
|
||||
{
|
||||
private readonly AzureActiveDirectoryOptions _azureOptions;
|
||||
|
||||
public ConfigureAzureOptions(IOptions<AzureActiveDirectoryOptions> azureOptions)
|
||||
{
|
||||
_azureOptions = azureOptions.Value;
|
||||
}
|
||||
|
||||
public void Configure(string name, JwtBearerOptions options)
|
||||
{
|
||||
options.Audience = _azureOptions.ClientId;
|
||||
options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
|
||||
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidAudience = _azureOptions.ValidAudience
|
||||
};
|
||||
}
|
||||
|
||||
public void Configure(JwtBearerOptions options)
|
||||
{
|
||||
Configure(Options.DefaultName, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public class CookieService
|
||||
{
|
||||
|
||||
public static void SetAuthenticationCookie(IResponseCookies cookies, string token)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
var expiry = new DateTimeOffset(DateTime.UtcNow.AddMinutes(15));
|
||||
var options = new CookieOptions
|
||||
{
|
||||
SameSite = SameSiteMode.Lax,
|
||||
HttpOnly = true,
|
||||
Secure = true,
|
||||
Expires = expiry,
|
||||
Domain = "landmarkcu.com"
|
||||
};
|
||||
cookies
|
||||
.Append(Constants.TokenPayload, token, options);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetAuthorizationCookie(IResponseCookies cookies, string authorization)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
var expiry = new DateTimeOffset(DateTime.UtcNow.AddMinutes(15));
|
||||
var options = new CookieOptions
|
||||
{
|
||||
SameSite = SameSiteMode.Lax,
|
||||
HttpOnly = false,
|
||||
Secure = true,
|
||||
Expires = expiry,
|
||||
Domain = "landmarkcu.com",
|
||||
};
|
||||
cookies
|
||||
.Append(Constants.TokenAuthorization, authorization, options);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExpireCookies(HttpContext context)
|
||||
{
|
||||
var currentCookies = context.Request.Cookies;
|
||||
var expiry = new DateTimeOffset(DateTime.UtcNow.AddDays(-1));
|
||||
var options = new CookieOptions
|
||||
{
|
||||
HttpOnly = false,
|
||||
Secure = true,
|
||||
Expires = expiry,
|
||||
Domain = ".landmarkcu.com"
|
||||
};
|
||||
if (currentCookies[Constants.TokenPayload] != null)
|
||||
{
|
||||
context.Response.Cookies
|
||||
.Delete(Constants.TokenPayload, options);
|
||||
}
|
||||
|
||||
if (currentCookies[Constants.TokenAuthorization] != null)
|
||||
{
|
||||
context.Response.Cookies
|
||||
.Delete(Constants.TokenAuthorization, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public static class CosmosIdService
|
||||
{
|
||||
public static LoanContainer AppendCosmosId(LoanContainer loanContainer, string cosmosId)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(cosmosId))
|
||||
{
|
||||
loanContainer.Id = cosmosId;
|
||||
}
|
||||
return loanContainer;
|
||||
}
|
||||
|
||||
public static LoanContainer RemoveCosmosId(LoanContainer loanContainer)
|
||||
{
|
||||
loanContainer.LoanId = null;
|
||||
return loanContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public static class ForwardedHeaderService
|
||||
{
|
||||
public static string GetRemoteIPAddress(HttpContext context)
|
||||
{
|
||||
var header = (context.Request.Headers["CF-Connecting-IP"].FirstOrDefault() ?? context.Request.Headers["X-Forwarded-For"].FirstOrDefault());
|
||||
if (IPAddress.TryParse(header, out IPAddress ip))
|
||||
{
|
||||
return ip.ToString();
|
||||
}
|
||||
return context.Connection.RemoteIpAddress.ToString();
|
||||
}
|
||||
|
||||
public static LoanContainer AppendIPAddress(LoanContainer loanContainer, HttpContext context)
|
||||
{
|
||||
loanContainer.LoanApplication.IPAddress = GetRemoteIPAddress(context);
|
||||
return loanContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public static class MemberIdService
|
||||
{
|
||||
public static LoanContainer AppendMemberInfo(LoanContainer loanContainer, OnlineBankingToken token)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(token?.MemberID) )
|
||||
{
|
||||
loanContainer.MemberId = token.MemberID;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(token?.IndividualID) )
|
||||
{
|
||||
loanContainer.IndividualId = token.IndividualID;
|
||||
}
|
||||
return loanContainer;
|
||||
}
|
||||
|
||||
public static LoanContainer RemoveMemberId(LoanContainer loanContainer)
|
||||
{
|
||||
loanContainer.MemberId = null;
|
||||
loanContainer.IndividualId = null;
|
||||
return loanContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog;
|
||||
using OnlineBankingAuth.Tokens;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public class RenewAuthenticatedSessionMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public RenewAuthenticatedSessionMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, ITokenService tokenService)
|
||||
{
|
||||
await _next(context);
|
||||
try
|
||||
{
|
||||
CookieService.SetAuthenticationCookie(context.Response.Cookies, tokenService.RenewOnlineBankingJWT(context));
|
||||
CookieService.SetAuthorizationCookie(context.Response.Cookies, tokenService.RenewAuthorization(context));
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Fatal("Error renewing cookied", ex);
|
||||
// Could not refresh token :-( no biggie?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
namespace Landmark.OLA.Application.Configuration
|
||||
{
|
||||
public class ApplicationSettings
|
||||
{
|
||||
public string AppName { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string SeqUrl { get; set; }
|
||||
public string SeqKey { get; set; }
|
||||
public string LogFilePath { get; set; }
|
||||
public string LogFileInterval { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace Landmark.OLA.Application.Configuration
|
||||
{
|
||||
public class BlobStorageConnectionSettings
|
||||
{
|
||||
public string AccountName { get; set; }
|
||||
public string AccountKey { get; set; }
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Landmark.OLA.Application.Configuration
|
||||
{
|
||||
public class BlobStorageSettings
|
||||
{
|
||||
public float MaxFileSizeInMB { get; set; }
|
||||
public List<string> AllowedFileTypes { get; set; }
|
||||
public int FileLimit { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Landmark.OLA.Application.Configuration
|
||||
{
|
||||
public class DealershipEmailSettings
|
||||
{
|
||||
public string RedBannerMessage { get; set; }
|
||||
public List<string> Recipients { get; set; }
|
||||
public string ActionLink { get; set; }
|
||||
public string EmailAPIUrl { get; set; }
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Landmark.OLA.Application.Configuration
|
||||
{
|
||||
public class NotificationsApiConfiguration
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string SendEmail { get; set; }
|
||||
public List<string> ErrorRecipients { get; set; } = [];
|
||||
|
||||
[NotMapped]
|
||||
public string SendEmailEndpointUrl => Url + SendEmail;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Models.Addresses;
|
||||
using Landmark.OLA.Models.Addresses.Validators;
|
||||
using Landmark.OLA.Models.Vendor;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class AddressValidationController : ControllerBase
|
||||
{
|
||||
private readonly IUspsVendorClient UspsVendor;
|
||||
|
||||
public AddressValidationController(IUspsVendorClient uspsVendor)
|
||||
{
|
||||
UspsVendor = uspsVendor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to correct a postal address
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a service corrected postal address, given a postal address.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(Address), 201)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
public async Task<IActionResult> Post([FromBody] Address address)
|
||||
{
|
||||
try
|
||||
{
|
||||
var validator = new AddressValidator();
|
||||
|
||||
var validatorResult = validator.Validate(address);
|
||||
|
||||
if (!validatorResult.IsValid)
|
||||
{
|
||||
foreach (var error in validatorResult.Errors)
|
||||
{
|
||||
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
|
||||
}
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var result = await UspsVendor.CompareAddress(address);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
Log.Error(te.Message);
|
||||
return BadRequest("The time taken to submit the address to the vendor has exceeded the configured timeout length.");
|
||||
}
|
||||
catch (ArgumentException )
|
||||
{
|
||||
// This happens when the address is valid in form, but not found.
|
||||
return StatusCode(204);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode(204);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Application.DataAccess;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.Loan.Validators;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class BranchConfigController : ControllerBase
|
||||
{
|
||||
private readonly IBranchDataAccess branchStorage;
|
||||
|
||||
public BranchConfigController(
|
||||
IBranchDataAccess branchDataAccess)
|
||||
{
|
||||
branchStorage = branchDataAccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get the branch configuration
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a full branch configuration.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None.
|
||||
/// </remarks>
|
||||
// GET: api/BranchConfig
|
||||
[HttpGet(Name = "GetBranchConfig")]
|
||||
[ProducesResponseType(typeof(BranchConfig), 200)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
BranchConfig config = branchStorage.GetBranchConfig();
|
||||
return Ok(config);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get the branch names configuration
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a full branch configuration.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None.
|
||||
/// </remarks>
|
||||
// GET: api/BranchNameConfig
|
||||
[HttpGet("BranchNameConfig", Name = "GetBranchNameConfig")]
|
||||
[ProducesResponseType(typeof(BranchNameConfig), 200)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult GetBranchNameConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
BranchNameConfig config = branchStorage.GetBranchNameConfig();
|
||||
return Ok(config);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to save the initial branch config.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a saved branch config with id. This call will fail if ANY configs currently exist.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Requires Azure AD Jwt token and admin role.
|
||||
/// </remarks>
|
||||
// POST: api/BranchConfig
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(string), 201)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public object Post([FromBody] BranchConfig branchConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (branchConfig != null)
|
||||
{
|
||||
var validator = new BranchConfigValidator();
|
||||
var validatorResult = validator.Validate(branchConfig);
|
||||
|
||||
var catchError = "";
|
||||
if (!validatorResult.IsValid)
|
||||
{
|
||||
foreach (var error in validatorResult.Errors)
|
||||
{
|
||||
catchError += error;
|
||||
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
|
||||
}
|
||||
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
branchConfig.Id = (Guid.NewGuid()).ToString();
|
||||
branchConfig.Date = DateTime.Now;
|
||||
branchStorage.CreateBranchConfig(branchConfig);
|
||||
|
||||
HttpContext.Response.StatusCode = 201;
|
||||
return new {id = branchConfig.Id, message = "Successfully saved document"};
|
||||
}
|
||||
|
||||
return BadRequest("Branch Config is required.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use update the branch config
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a status after attempting to update the branch configuration.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
|
||||
/// </remarks>
|
||||
// PUT: api/Storage
|
||||
[HttpPut]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public IActionResult Put([FromBody] BranchConfig branchConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var validator = new BranchConfigValidator();
|
||||
var validatorResult = validator.Validate(branchConfig);
|
||||
|
||||
if (!validatorResult.IsValid)
|
||||
{
|
||||
foreach (var error in validatorResult.Errors)
|
||||
{
|
||||
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
|
||||
}
|
||||
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var cosmosId = branchConfig?.Id;
|
||||
if (!string.IsNullOrEmpty(cosmosId))
|
||||
{
|
||||
branchConfig.Date = DateTime.Now;
|
||||
branchStorage.UpdateBranchConfig(branchConfig);
|
||||
return CreatedAtAction(nameof(Put), new {id = branchConfig.Id}, branchConfig);
|
||||
}
|
||||
|
||||
return BadRequest("Branch config invalid.");
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get an updated branch names configuration
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a full branch names configuration after updating it internally.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None.
|
||||
/// </remarks>
|
||||
// GET: api/UpdateBranchNameConfig
|
||||
[HttpGet("UpdateBranchNameConfig", Name = "GetUpdateBranchNameConfig")]
|
||||
[ProducesResponseType(typeof(BranchNameConfig), 200)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult UpdateBranchNameConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
branchStorage.UpdateBranchNameConfig();
|
||||
BranchNameConfig config = branchStorage.GetBranchNameConfig();
|
||||
return Ok(config);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using CRIF.ACTion.Services.Crif;
|
||||
using Landmark.OLA.Application.Common.Security;
|
||||
using Landmark.OLA.Application.DataAccessLayer;
|
||||
using Landmark.OLA.Models.Decisioning;
|
||||
using Landmark.OLA.Models.Decisioning.Enums;
|
||||
using Landmark.OLA.Models.Loan.Enums;
|
||||
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/LoanApplication/{loanApplicationId}/DecisioningDetails")]
|
||||
[ApiController]
|
||||
public class DecisioningController : ControllerBase
|
||||
{
|
||||
private readonly ICrifVendorClient VendorClient;
|
||||
private readonly ILoanApplicationDataAccess LoanApplicationStorage;
|
||||
|
||||
public DecisioningController(
|
||||
ICrifVendorClient vendorClient,
|
||||
ILoanApplicationDataAccess loanApplicationDataAccess)
|
||||
{
|
||||
VendorClient = vendorClient;
|
||||
LoanApplicationStorage = loanApplicationDataAccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to start decisioning on a loan
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns status of decision start, on 202 you can begin requesting details.
|
||||
/// Cookies: Will expire existing cookies.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// PUT: api/Application/{loanApplicationId}/DecisioningDetails
|
||||
[HttpPut]
|
||||
[ProducesResponseType(202)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Put(string loanApplicationId, [FromBody] DecisioningDetails decisioningDetails)
|
||||
{
|
||||
if (decisioningDetails.DecisioningState != DecisioningState.Requested)
|
||||
{
|
||||
Log.Error("DecisioningState was not set to Requested.");
|
||||
return BadRequest("Must PUT a DecisioningState of Requested.");
|
||||
}
|
||||
|
||||
Log.Information($"Sending loan {loanApplicationId} to decisioning.");
|
||||
try
|
||||
{
|
||||
var decisioningRequestSuccess = await VendorClient.SendToDecisioning(loanApplicationId);
|
||||
|
||||
if (decisioningRequestSuccess)
|
||||
{
|
||||
CookieService.ExpireCookies(HttpContext);
|
||||
return new AcceptedResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
// SendToDecisioning did not succeed, "That's an error. That's all we know.", Check the server logs!
|
||||
Log.Error($"Loan #{loanApplicationId} could not be submitted to decisioning.");
|
||||
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
Log.Error(te.Message);
|
||||
return BadRequest(
|
||||
"The time taken to submit the loan application to the vendor has exceeded the configured timeout length.");
|
||||
}
|
||||
catch (InvalidOperationException ioe)
|
||||
{
|
||||
Log.Error(ioe.Message);
|
||||
return BadRequest(ioe.Message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get details of a decision
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a decision status, which may be empty if the decision is not ready.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// GET: api/Application/{loanApplicationId}/DecisioningDetails
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(DecisioningDetails), 200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Get(string loanApplicationId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var decisioningResult = await VendorClient.GetDecisionStatus(loanApplicationId);
|
||||
if (decisioningResult.DecisioningState == DecisioningState.Ready)
|
||||
{
|
||||
var cosmosId = LoanApplicationStorage.GetContainerIdByLoanId(loanApplicationId);
|
||||
LoanApplicationStorage.UpdateStatus(cosmosId, LoanStatus.Decisioned);
|
||||
}
|
||||
|
||||
return Ok(decisioningResult);
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
Log.Error(te.Message);
|
||||
return BadRequest(
|
||||
"The time taken to decision the loan application has exceeded the configured timeout length.");
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
Log.Error(ae.Message);
|
||||
return BadRequest(ae.Message);
|
||||
}
|
||||
catch (Error13Exception e13)
|
||||
{
|
||||
// We will remove these logs on
|
||||
Log.Error(e13.Message);
|
||||
var decisionResult = new DecisioningDetails()
|
||||
{
|
||||
DecisioningState = DecisioningState.Unknown,
|
||||
DecisioningResult = DecisioningResult.ProcessStatus_Incomplete
|
||||
};
|
||||
return Ok(decisionResult);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Application.Services;
|
||||
using Landmark.OLA.Application.Services.Exceptions;
|
||||
using Landmark.OLA.Models.Individuals;
|
||||
using OnlineBankingAuth.Tokens;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class IndividualController : ControllerBase
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly ITokenService _tokenService;
|
||||
|
||||
public IndividualController(ITokenService tokenService,
|
||||
ICoreService coreService)
|
||||
{
|
||||
_coreService = coreService;
|
||||
_tokenService = tokenService;
|
||||
}
|
||||
|
||||
// GET: api/Individual/self
|
||||
[HttpGet("self", Name = "GetIndividual")]
|
||||
[ProducesResponseType(typeof(Individual), 200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
[ProducesResponseType(503)]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
if (token?.IndividualID == null )
|
||||
{
|
||||
Log.Error("Unauthorized request for 'Self' in Loan API, denying.");
|
||||
if (token?.MemberID != null)
|
||||
{
|
||||
// This isn't logging the security token, but the actual token content.
|
||||
Log.Error($"Unauthorized request was on behalf of {@token}");
|
||||
}
|
||||
return Unauthorized();
|
||||
}
|
||||
var individual = await _coreService.GetIndividual(token.MemberID);
|
||||
return Ok(individual);
|
||||
}
|
||||
catch (CoreUnavailableException cue)
|
||||
{
|
||||
Log.Error(cue.Message);
|
||||
return StatusCode((int)HttpStatusCode.ServiceUnavailable);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using CRIF.ACTion.Services.Crif;
|
||||
using Landmark.OLA.Application.Configuration;
|
||||
using Landmark.OLA.Application.DataAccess;
|
||||
using Landmark.OLA.Application.DataAccessLayer;
|
||||
using Landmark.OLA.Application.Services.MicroApiServices;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.Loan.Enums;
|
||||
using Landmark.OLA.Models.Loan.Validators;
|
||||
using OnlineBankingAuth.Tokens;
|
||||
using LCU.Library.Internal.NotificationsModels.Models;
|
||||
using System.Net;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class LoanApplicationController : ControllerBase
|
||||
{
|
||||
private readonly ICrifVendorClient VendorClient;
|
||||
private readonly ILoanApplicationDataAccess loanApplicationStorage;
|
||||
private readonly IBranchDataAccess branchStorage;
|
||||
private readonly IBlobStorageAccess _blobStorageAccess;
|
||||
private readonly ITokenService _tokenService;
|
||||
readonly IOptions<DealershipEmailSettings> _dealershipEmailSettings;
|
||||
private readonly INotificationsApiService _notificationsApiService;
|
||||
private readonly Services.Email.IEmailTemplateService _emailTemplateService;
|
||||
|
||||
public LoanApplicationController(
|
||||
ICrifVendorClient vendorClient,
|
||||
ILoanApplicationDataAccess loanApplicationDataAccess,
|
||||
IBranchDataAccess branchDataAccess,
|
||||
IBlobStorageAccess blobStorageAccess,
|
||||
ITokenService tokenService,
|
||||
IOptions<DealershipEmailSettings> dealershipEmailSettings,
|
||||
INotificationsApiService notificationsApiService,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
Services.Email.IEmailTemplateService emailTemplateService)
|
||||
{
|
||||
VendorClient = vendorClient;
|
||||
loanApplicationStorage = loanApplicationDataAccess;
|
||||
branchStorage = branchDataAccess;
|
||||
_blobStorageAccess = blobStorageAccess;
|
||||
_tokenService = tokenService;
|
||||
_dealershipEmailSettings = dealershipEmailSettings;
|
||||
_notificationsApiService = notificationsApiService;
|
||||
_emailTemplateService = emailTemplateService;
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("AuthenticateDealership")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> AuthenticateDealership([FromBody] DealershipAuthRequest authRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(authRequest.ApplicationId))
|
||||
{
|
||||
return BadRequest("ApplicationId is required.");
|
||||
}
|
||||
|
||||
authRequest.DealershipName = "";
|
||||
|
||||
var authResponse = await VendorClient.GetDealershipAuthDetails(authRequest.ApplicationId);
|
||||
|
||||
if (authRequest.Equals(authResponse))
|
||||
{
|
||||
// This portion of the try can be uncommented so that the "session" of an upload won't always be re-created.
|
||||
// There may be a small bug that might only affect app# 10056 if this is re-enabled. Basically the blob file was deleted, but the cosmos record has a reference.
|
||||
// deleting the cosmos record should resolve the issue
|
||||
|
||||
/*try
|
||||
{
|
||||
var loanApplicationId = loanApplicationStorage.GetContainerIdByLoanId(authRequest.ApplicationId);
|
||||
return Ok(new { cosmosId = loanApplicationId });
|
||||
}
|
||||
catch (KeyNotFoundException exception)
|
||||
{*/
|
||||
var token = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
var loanContainer = new LoanContainer(new LoanApplication())
|
||||
{
|
||||
Id = (Guid.NewGuid()).ToString(),
|
||||
LoanId = authRequest.ApplicationId
|
||||
};
|
||||
|
||||
loanApplicationStorage.CreateContainer(loanContainer);
|
||||
|
||||
HttpContext.Response.StatusCode = 201;
|
||||
|
||||
return Created($"/api/LoanApplication/AuthenticateDealer/{loanContainer.Id}", new { cosmosId = loanContainer.Id });
|
||||
/*}*/
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("SubmitDealershipFiles/{cosmosId}")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> SubmitDealershipFiles(string cosmosId, [FromBody] DealershipAuthRequest authRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(cosmosId))
|
||||
{
|
||||
return BadRequest("Cosmos Id is required.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(authRequest.ApplicationId))
|
||||
{
|
||||
return BadRequest("ApplicationId is required.");
|
||||
}
|
||||
|
||||
authRequest.DealershipName = "";
|
||||
|
||||
var authResponse = await VendorClient.GetDealershipAuthDetails(authRequest.ApplicationId);
|
||||
|
||||
if (authRequest.Equals(authResponse))
|
||||
{
|
||||
LoanContainer loanContainer = loanApplicationStorage.GetContainerById(cosmosId);
|
||||
LoanApplication loanApplication = loanContainer.LoanApplication;
|
||||
loanApplication.Id = authResponse.ApplicationId;
|
||||
|
||||
// Submit files to ACTion, 1-by-1. Each time the POST request is called, it will upload 1 file. Once the last file has been sent, an email will be sent
|
||||
if (loanApplication.Files.Count > 0)
|
||||
{
|
||||
if (!(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")?.Equals("Production", StringComparison.OrdinalIgnoreCase) ?? false) &&
|
||||
loanApplication.Files[0].Name.Equals("do-not-allow-transmission.jpg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
var fileBase64Value = _blobStorageAccess.GetBlobAsBase64(loanApplication.Files[0]);
|
||||
|
||||
var fileSubmitted = await VendorClient.AddFile(loanApplication.Id, loanApplication.Files[0], fileBase64Value);
|
||||
|
||||
if (fileSubmitted)
|
||||
{
|
||||
_blobStorageAccess.DeleteBlob(loanApplication.Files[0]);
|
||||
loanApplication.Files.RemoveAt(0);
|
||||
loanApplicationStorage.UpdateContainer(loanContainer);
|
||||
|
||||
// Return the files if there are still some left to upload. If not, send the email and return
|
||||
if (loanApplication.Files.Count > 0)
|
||||
{
|
||||
return StatusCode(201, new { files = loanApplication.Files });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"File not sent to ACTion, not deleting file: {loanApplication.Files[0]}");
|
||||
}
|
||||
}
|
||||
|
||||
if (_dealershipEmailSettings.Value.Recipients != null && _dealershipEmailSettings.Value.Recipients.Count > 0 && !_dealershipEmailSettings.Value.Recipients.Contains("PERENVIRONMENT") && !string.IsNullOrEmpty(_dealershipEmailSettings.Value.ActionLink) && !string.IsNullOrEmpty(_dealershipEmailSettings.Value.EmailAPIUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
var redBannerMessage = _dealershipEmailSettings.Value.RedBannerMessage ?? string.Empty;
|
||||
|
||||
var contentHtml =
|
||||
"This is an auto generated message. <br><br>" +
|
||||
"A new deal has been submitted and is ready to be viewed and exported <br><br>" +
|
||||
$"Dealership Name: <strong>{authResponse.DealershipName}</strong><br>" +
|
||||
$"Application ID: <strong>{authResponse.ApplicationId}</strong><br>" +
|
||||
$"Primary Applicant's Last Name or Business Full Name: <strong>{authResponse.ApplicantLastName}</strong><br><br>" +
|
||||
$"Click on the following link to open the application record within ACTion: <a href=\"{_dealershipEmailSettings.Value.ActionLink}{authResponse.ApplicationId}\">{authResponse.ApplicationId}</a><br>";
|
||||
|
||||
var requestBody = _emailTemplateService.BuildBrandedBody(contentHtml, redBannerMessage);
|
||||
|
||||
var request = new EmailRequest()
|
||||
{
|
||||
Subject = $"Dealership has uploaded documents to application {loanApplication.Id}",
|
||||
Recipient = _dealershipEmailSettings.Value.Recipients,
|
||||
Body = requestBody
|
||||
};
|
||||
|
||||
Log.Information($"Sending email to {_dealershipEmailSettings.Value.Recipients} for application ID: {loanApplication.Id}");
|
||||
|
||||
await _notificationsApiService.SendEmailAsync(request);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Error when sending email to loan team for application ID {loanApplication.Id}");
|
||||
Log.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
loanApplicationStorage.DeleteApplication(cosmosId);
|
||||
|
||||
return StatusCode(200, new { files = loanApplication.Files });
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to transmit an existing loan application to CRIF Lending
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Transmits a loan application to CRIF lending given a loan storage id (not a loan id).
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// Post: api/LoanApplication/Submit/:id
|
||||
[HttpPost("Submit/{cosmosId}")]
|
||||
[ProducesResponseType(typeof(LoanApplication), 201)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(520)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Post(string cosmosId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(cosmosId))
|
||||
{
|
||||
return BadRequest("Id is required.");
|
||||
}
|
||||
|
||||
LoanContainer loanContainer = loanApplicationStorage.GetContainerById(cosmosId);
|
||||
if ( (loanContainer.Status != LoanStatus.In_Progress && loanContainer.Status != LoanStatus.Vendor_Error
|
||||
&& loanContainer.Status != LoanStatus.Application_Error) || loanContainer.LoanApplication == null)
|
||||
{
|
||||
return BadRequest("Loan is not in progress. Unable to submit.");
|
||||
}
|
||||
|
||||
LoanApplication loanApplication = loanContainer.LoanApplication;
|
||||
var memberInfo = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
if (memberInfo != null)
|
||||
{
|
||||
loanApplication.PrimaryBorrower.CustomerNumber = memberInfo.MemberID;
|
||||
}
|
||||
|
||||
var validator = new LoanApplicationValidator();
|
||||
var validatorResult = validator.Validate(loanApplication);
|
||||
|
||||
var catchError = "";
|
||||
if (!validatorResult.IsValid)
|
||||
{
|
||||
foreach (var error in validatorResult.Errors)
|
||||
{
|
||||
catchError += error;
|
||||
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
|
||||
}
|
||||
loanApplicationStorage.UpdateError(loanContainer.Id, new LoanError(LoanErrorCode.Error_Submitting, catchError));
|
||||
loanApplicationStorage.UpdateStatus(loanContainer.Id, LoanStatus.Application_Error);
|
||||
Log.Error($"Could not submit application, errors follow: {@ModelState}");
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
// CRIF id will be assigned
|
||||
loanApplication.Id = await VendorClient.CreateApplicationAndGetId(loanApplication, branchStorage);
|
||||
|
||||
// Submit files to ACTion
|
||||
foreach(var file in loanApplication.Files)
|
||||
{
|
||||
var fileBase64Value = _blobStorageAccess.GetBlobAsBase64(file);
|
||||
|
||||
var fileSubmitted = true;
|
||||
|
||||
bool shouldUploadFileToAction = false;
|
||||
|
||||
var vehicleTypes = new List<LoanType> { LoanType.V_Vehicle, LoanType.V_VehiclePreapproval, LoanType.P_Powersport, LoanType.T_TrailerMotorhome, LoanType.M_Boat };
|
||||
if (vehicleTypes.Contains((LoanType)loanApplication.LoanType))
|
||||
{
|
||||
if (file.Description.Equals("DNR Registration Card", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
shouldUploadFileToAction = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldUploadFileToAction = loanApplication.Purpose == LoanPurpose.Purpose.Purchase;
|
||||
}
|
||||
} else if (loanApplication.LoanType != LoanType.EQ_HELOC_Flex)
|
||||
{
|
||||
shouldUploadFileToAction = true;
|
||||
} else if (loanApplication.LoanType == LoanType.EQ_HELOC_Flex && loanApplication.RequestedAmount > 100000)
|
||||
{
|
||||
shouldUploadFileToAction = true;
|
||||
} else if (!file.Description.Equals("Proof of Income", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
shouldUploadFileToAction = true;
|
||||
}
|
||||
|
||||
if (shouldUploadFileToAction)
|
||||
{
|
||||
fileSubmitted = await VendorClient.AddFile(loanApplication.Id, file, fileBase64Value);
|
||||
}
|
||||
|
||||
if(fileSubmitted)
|
||||
{
|
||||
_blobStorageAccess.DeleteBlob(file);
|
||||
} else
|
||||
{
|
||||
Log.Error($"File not sent to ACTion, not deleting file: {file}");
|
||||
}
|
||||
}
|
||||
|
||||
loanApplicationStorage.UpdateStatus(cosmosId, LoanStatus.Submitted);
|
||||
loanApplicationStorage.UpdateError(loanContainer.Id, new LoanError(LoanErrorCode.No_Error, "No_Error"));
|
||||
loanApplicationStorage.UpdateLoanId(loanContainer.Id, loanApplication.Id);
|
||||
loanApplicationStorage.DeleteApplication(cosmosId);
|
||||
|
||||
return Ok(new { loanApplicationId = loanApplication.Id });
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound("An application with that Id could not be found");
|
||||
}
|
||||
catch (InvalidDataException ie)
|
||||
{
|
||||
loanApplicationStorage.UpdateError(cosmosId, new LoanError(LoanErrorCode.Error_Submitting, ie.Message));
|
||||
loanApplicationStorage.UpdateStatus(cosmosId, LoanStatus.Application_Error);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
loanApplicationStorage.UpdateError(cosmosId, new LoanError(LoanErrorCode.Error_Submitting, "The time taken to submit the loan application to the vendor has exceded the configured timeout length."));
|
||||
Log.Error(te.Message);
|
||||
loanApplicationStorage.UpdateStatus(cosmosId, LoanStatus.Vendor_Error);
|
||||
return BadRequest("The time taken to submit the loan application to the vendor has exceeded the configured timeout length.");
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
loanApplicationStorage.UpdateError(cosmosId, new LoanError(LoanErrorCode.Error_Submitting, "Invalid argument."));
|
||||
loanApplicationStorage.UpdateStatus(cosmosId, LoanStatus.Vendor_Error);
|
||||
Log.Error(ae.Message);
|
||||
return BadRequest(ae.Message);
|
||||
}
|
||||
catch (FormatException fe)
|
||||
{
|
||||
loanApplicationStorage.UpdateError(cosmosId, new LoanError(LoanErrorCode.Error_Submitting, "Vendor returned unexpected response."));
|
||||
loanApplicationStorage.UpdateStatus(cosmosId, LoanStatus.Vendor_Error);
|
||||
Log.Error(fe.Message);
|
||||
return StatusCode(520, fe.Message);
|
||||
//Cloudflare uses an HTTP 520 when the external "server returns something unexpected"[1]
|
||||
//We return this when we don't understand CRIF's response
|
||||
//[1] https://support.cloudflare.com/hc/en-us/articles/206973867-Error-analytics-by-colo
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
loanApplicationStorage.UpdateError(cosmosId, new LoanError(LoanErrorCode.Error_Submitting, "Unexpected exception when submitting application."));
|
||||
loanApplicationStorage.UpdateStatus(cosmosId, LoanStatus.Vendor_Error);
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to transmit a new loan application directly to CRIF Lending
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Transmits a loan application directly to CRIF Lending given a valid loan application.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// Post: api/LoanApplication/
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(LoanApplication), 201)]
|
||||
[ProducesResponseType(typeof(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary), 400)]
|
||||
[ProducesResponseType(520)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Post([FromBody] LoanApplication loanApplication)
|
||||
{
|
||||
try
|
||||
{
|
||||
var validator = new LoanApplicationValidator();
|
||||
var validatorResult = validator.Validate(loanApplication);
|
||||
|
||||
if (!validatorResult.IsValid)
|
||||
{
|
||||
foreach (var error in validatorResult.Errors)
|
||||
{
|
||||
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
|
||||
}
|
||||
Log.Error($"Could not submit application, [Possibly third party], errors follow: {@ModelState}");
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var application = await VendorClient.CreateApplication(loanApplication, branchStorage);
|
||||
|
||||
return CreatedAtRoute("GetApplication", new { loanApplicationId = application.Id }, application);
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
Log.Error(te.Message);
|
||||
return BadRequest("The time taken to submit the loan application to the vendor has exceeded the configured timeout length.");
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
Log.Error(ae.Message);
|
||||
return BadRequest(ae.Message);
|
||||
}
|
||||
catch (FormatException fe)
|
||||
{
|
||||
Log.Error(fe.Message);
|
||||
return StatusCode(520, fe.Message);
|
||||
//Cloudflare uses an HTTP 520 when the external "server returns something unexpected"[1]
|
||||
//We return this when we don't understand CRIF's response
|
||||
//[1] https://support.cloudflare.com/hc/en-us/articles/206973867-Error-analytics-by-colo
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Landmark.OLA.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class LoggingController : ControllerBase
|
||||
{
|
||||
public LoggingController()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to send something to the logger from the client
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Logs a message from a client.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None set or required [yet]. It may be best to require a token here, but then we'll miss startup errors.
|
||||
/// </remarks>
|
||||
// Post: api/Logging/Error
|
||||
[HttpPost("Error")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Post([FromBody] ClientError error)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Error(error.Message, error.StackTrace);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
public class OnlineIdentityFormData
|
||||
{
|
||||
public string DateOfBirth { get; set; }
|
||||
public string SocialSecurityNumber { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Models;
|
||||
using Landmark.OLA.Application.Services;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/GetPdf/")]
|
||||
[ApiController]
|
||||
public class PdfController : ControllerBase
|
||||
{
|
||||
private readonly IPdfService _pdfService;
|
||||
private readonly ApplicationControlConfiguration _appControlConfig;
|
||||
|
||||
public PdfController(IOptions<ApplicationControlConfiguration> settings,
|
||||
IPdfService pdfService)
|
||||
{
|
||||
_pdfService = pdfService;
|
||||
_appControlConfig = settings.Value;
|
||||
}
|
||||
|
||||
// Get: api/GetPdf/ImportantTerms
|
||||
[HttpGet("ImportantTerms")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult GetPdfImportantTerms()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = _pdfService.GetFileBytes(_appControlConfig.ImportantTermsPdfFilePath);
|
||||
var payload = new FileContentResult(result, "application/octet-stream");
|
||||
return payload;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
Log.Error(ae, $"Request for bad file path for IMPORTANT TERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf");
|
||||
return BadRequest("File path not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Unexpected error retrieving file path IMPORTANT TERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
// Get: api/GetPdf/WhatYouShouldKnow
|
||||
[HttpGet("WhatYouShouldKnow")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult GetPdfWhatYouShouldKnow()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = _pdfService.GetFileBytes(_appControlConfig.WhatYouShouldKnowPdfFilePath);
|
||||
var payload = new FileContentResult(result, "application/octet-stream");
|
||||
return payload;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
Log.Error(ae, $"Request for bad file path What You Should Know About Home Equity Lines of Credit Revised 8-2022.pdf");
|
||||
return BadRequest("File path not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Unexpected error retrieving file path What You Should Know About Home Equity Lines of Credit Revised 8-2022.pdf");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.Security.Authentication;
|
||||
using Serilog;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OnlineBankingAuth.Tokens;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
using Landmark.OLA.Application.Common.Security;
|
||||
using Landmark.OLA.Models.Core;
|
||||
using OnlineBankingAuth.Parsers;
|
||||
using OnlineBankingAuth.Services;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class SamlAssertionConsumerController : ControllerBase
|
||||
{
|
||||
private readonly IDigitalInsightVendorClient _diVendor;
|
||||
private readonly ITokenService _tokenService;
|
||||
|
||||
public SamlAssertionConsumerController
|
||||
(
|
||||
IDigitalInsightVendorClient diVendor,
|
||||
ITokenService tokenService
|
||||
)
|
||||
{
|
||||
_diVendor = diVendor;
|
||||
_tokenService = tokenService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to refer a user to the application with a valid SAML assertion
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns an application redirect, adding in a SAML assertion via a JWT in a secure http-only cookie.
|
||||
/// Cookies: Set a standard pair of authentication and authorization cookies.
|
||||
/// Security: Passed document must include a valid, signed SAML assertion, provides member authentication.
|
||||
/// </remarks>
|
||||
// POST: api/SAMLAssertionConsumer
|
||||
[HttpPost]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 401)]
|
||||
public async Task Post([FromForm] SAMLMessaging document)
|
||||
{
|
||||
try
|
||||
{
|
||||
var decodedString = Encoding.UTF8.GetString(Convert.FromBase64String(document.SAMLResponse));
|
||||
|
||||
var parsedXml = XElement.Parse(decodedString);
|
||||
var xmlDocument = new XmlDocument
|
||||
{
|
||||
PreserveWhitespace = true
|
||||
};
|
||||
xmlDocument.Load(parsedXml.CreateReader());
|
||||
|
||||
// @TODO: Should catch error here, move element query to static service #1400b
|
||||
var x509Payload = (XmlElement)xmlDocument.GetElementsByTagName("ds:X509Certificate")[0];
|
||||
var vendorCertMatches = _diVendor.ValidateDIPublicKey(x509Payload);
|
||||
if (!vendorCertMatches)
|
||||
{
|
||||
throw new AuthenticationException("The vendor's public certificate did not match! ");
|
||||
}
|
||||
|
||||
var assertion = _diVendor.GetSAMLPayload(xmlDocument);
|
||||
|
||||
// Check that assertion is still valid
|
||||
var datesValid = _diVendor.ValidateTimeStamp(assertion);
|
||||
if (!datesValid)
|
||||
{
|
||||
throw new AuthenticationException("The Authentication has expired!");
|
||||
}
|
||||
|
||||
var obToken = new OnlineBankingToken(assertion, null, true)
|
||||
{
|
||||
IsTwoFactor = true
|
||||
};
|
||||
obToken.IndividualID = obToken.MemberID;
|
||||
|
||||
AssignTokens(obToken);
|
||||
HttpContext.Response.Redirect(_diVendor.GetSuccessRedirectUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure to validate SAML in {GetType().Name}");
|
||||
HttpContext.Response.Redirect(_diVendor.GetFailureRedirectUrl(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private void AssignTokens(OnlineBankingToken obToken)
|
||||
{
|
||||
var meta = new MemberMeta
|
||||
{
|
||||
IsTwoFactor = obToken.IsTwoFactor,
|
||||
IsMember = (obToken.MemberID != null),
|
||||
IsIndividual = (obToken.IndividualID != null)
|
||||
};
|
||||
|
||||
var privateToken = _tokenService.CreateOnlineBankingJWT(obToken);
|
||||
var publicToken = _tokenService.CreateAuthMetaToken(meta);
|
||||
CookieService.SetAuthenticationCookie(HttpContext.Response.Cookies, privateToken);
|
||||
CookieService.SetAuthorizationCookie(HttpContext.Response.Cookies, publicToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to create an empty cookie session
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a pair of empty authorization and authentication cookies.
|
||||
/// Cookies: Sets empty Authentication and Authorization cookies.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
[HttpGet("InitSession")]
|
||||
[ProducesResponseType(200)]
|
||||
public IActionResult Get()
|
||||
{
|
||||
var obToken = new OnlineBankingToken
|
||||
{
|
||||
MemberID = null, IndividualID = null, FI_ID = "0", IsTwoFactor = false
|
||||
};
|
||||
|
||||
AssignTokens(obToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to create a cookie session with credentials
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a pair of authorization and authentication cookies when provided with valid credentials.
|
||||
/// Cookies: Sets Authentication and Authorization cookies.
|
||||
/// Security: Require.
|
||||
/// </remarks>
|
||||
[HttpPost("Authenticate")]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 401)]
|
||||
public async Task<object> PostAsync([FromBody] OnlineBankingFormData document)
|
||||
{
|
||||
try
|
||||
{
|
||||
var diResponse = await _diVendor.AuthenticateOnlineBankingAccount(document);
|
||||
|
||||
if (diResponse.Token == null)
|
||||
{
|
||||
Log.Error($"Member failed non-two factor login, {@document.Username}");
|
||||
return Ok(diResponse.Status);
|
||||
}
|
||||
|
||||
var assertion = new DISAMLAssertion
|
||||
{
|
||||
MemberID = diResponse.Token.DiMemberNumber,
|
||||
FI_ID = diResponse.Token.DiFiid
|
||||
};
|
||||
|
||||
var obToken = new OnlineBankingToken(assertion, null, false)
|
||||
{
|
||||
IsTwoFactor = false
|
||||
};
|
||||
obToken.IndividualID = obToken.MemberID;
|
||||
|
||||
AssignTokens(obToken);
|
||||
return Ok(diResponse.Status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure to authenticate user in {GetType().Name}");
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to identify a unique individual on the member account.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns OK if an individual ID was added to the cookie.
|
||||
/// Cookies: Sets Individual ID in cookie.
|
||||
/// Security: Requires existing cookie, with member.
|
||||
/// </remarks>
|
||||
[HttpPost("Identify")]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(typeof(ModelStateDictionary), 401)]
|
||||
public async Task<object> PostIdentify([FromBody] OnlineIdentityFormData document)
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
if (token == null)
|
||||
{
|
||||
Log.Error($"Non-authenticated attempt at individual confirmation! Using info {@document}, Refusing.");
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
token.IndividualID = token.MemberID;
|
||||
AssignTokens(token);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure to identify user in {GetType().Name}");
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Landmark.OLA.Application.Services;
|
||||
using Landmark.OLA.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class StatusController : ControllerBase
|
||||
{
|
||||
private readonly IStatus _status;
|
||||
private readonly ApplicationControlConfiguration _appControlConfig;
|
||||
private readonly CoreSettings _coreSettings;
|
||||
|
||||
public StatusController(IOptions<ApplicationControlConfiguration> settings,
|
||||
IStatus status,
|
||||
CoreSettings coreSettings)
|
||||
{
|
||||
_appControlConfig = settings.Value;
|
||||
_status = status;
|
||||
_coreSettings = coreSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load the root of the API result in a web-app redirect.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// GET: Root
|
||||
[HttpGet()]
|
||||
[ProducesResponseType(302)]
|
||||
public void GetRoot()
|
||||
{
|
||||
HttpContext.Response.Redirect(_appControlConfig.HomePageUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get the status of the api
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a simple object describing the build time, version, and environment of the running API.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// GET: api/Status
|
||||
[HttpGet("api/Status")]
|
||||
[ProducesResponseType(typeof(IStatus), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ObjectResult(_status);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get the core connection settings of the api
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a simple object describing the core settings being used by the API.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// GET: api/Status
|
||||
[HttpGet("api/GetCoreConnection")]
|
||||
[ProducesResponseType(typeof(CoreSettings), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult GetCoreConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ObjectResult(_coreSettings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+577
@@ -0,0 +1,577 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Application.Common.Security;
|
||||
using Landmark.OLA.Application.Configuration;
|
||||
using Landmark.OLA.Application.DataAccess;
|
||||
using Landmark.OLA.Application.DataAccessLayer;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.Loan.Enums;
|
||||
using OnlineBankingAuth.Tokens;
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class StorageController : ControllerBase
|
||||
{
|
||||
|
||||
private readonly ILoanApplicationDataAccess loanApplicationStorage;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly BlobStorageSettings _blobStorageSettings;
|
||||
private readonly IBlobStorageAccess _blobStorageAccess;
|
||||
|
||||
public StorageController(
|
||||
ILoanApplicationDataAccess loanApplicationDataAccess,
|
||||
ITokenService tokenService,
|
||||
IOptions<BlobStorageSettings> blobStorageSettings,
|
||||
IBlobStorageAccess blobStorageAccess)
|
||||
{
|
||||
loanApplicationStorage = loanApplicationDataAccess;
|
||||
_tokenService = tokenService;
|
||||
_blobStorageSettings = blobStorageSettings.Value;
|
||||
_blobStorageAccess = blobStorageAccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to retrieve a set of Loan Containers by filter
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns an array of Loan Containers given a filter
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
|
||||
/// </remarks>
|
||||
// Post: api/Storage/Filter
|
||||
[HttpPost("Filter")]
|
||||
[ProducesResponseType(typeof(IEnumerable<LoanContainer>), 200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public IActionResult Post([FromBody] Filter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(loanApplicationStorage.GetLoanContainers(filter));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get a specific loan container FOR AN ADMIN
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a loan container given a loan id.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Must have a valid JWT token retrieved from Azure AD.
|
||||
/// </remarks>
|
||||
// GET: api/Storage/5
|
||||
[HttpGet("{id}", Name = "Get")]
|
||||
[ProducesResponseType(typeof(LoanContainer), 200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public IActionResult Get(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(id))
|
||||
{
|
||||
return BadRequest("Id is required.");
|
||||
}
|
||||
LoanContainer container = loanApplicationStorage.GetContainerById(id);
|
||||
return Ok(container);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound("An application with that Id could not be found");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get loan containers for an authenticated member
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a list of loan containers given a valid cookie with member id.
|
||||
/// Cookies: Requires authenticated cookie with member id.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// GET: api/Storage
|
||||
[HttpGet("GetMemberApplications", Name = "GetMemberApplications")]
|
||||
[ProducesResponseType(typeof(List<LoanContainer>), 200)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult GetMemberApplications()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assertion = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
if (string.IsNullOrEmpty(assertion?.MemberID) || string.IsNullOrEmpty(assertion?.IndividualID))
|
||||
{
|
||||
Log.Error(assertion.MemberID + "/" + assertion.IndividualID, "no stacktrace");
|
||||
return StatusCode((int)System.Net.HttpStatusCode.Forbidden);
|
||||
}
|
||||
List<LoanContainer> containers = loanApplicationStorage.GetAllMemberContainers(assertion.MemberID, assertion.IndividualID);
|
||||
return Ok(containers);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to save a loan container
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a saved loan container with id and possibly member id
|
||||
/// Cookies: Authenticated member cookie optional, will cause id to be saved.
|
||||
/// Security: Currently creates authentication and authorization cookies.
|
||||
/// </remarks>
|
||||
// POST: api/Storage
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(string), 201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
public object Post([FromBody] LoanContainer loanContainer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (loanContainer != null)
|
||||
{
|
||||
var token = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
loanContainer.Id = (Guid.NewGuid()).ToString();
|
||||
loanContainer = MemberIdService.AppendMemberInfo(loanContainer, token);
|
||||
loanContainer = ForwardedHeaderService.AppendIPAddress(loanContainer, HttpContext);
|
||||
|
||||
loanApplicationStorage.CreateContainer(loanContainer);
|
||||
HttpContext.Response.StatusCode = 201;
|
||||
return new { id = loanContainer.Id, message = "Successfully saved document" };
|
||||
}
|
||||
return BadRequest("Loan Application is required.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("New")]
|
||||
[ProducesResponseType(typeof(string), 201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(500)]
|
||||
public object PostNew()
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
var loanContainer = new LoanContainer(new LoanApplication())
|
||||
{
|
||||
Id = (Guid.NewGuid()).ToString()
|
||||
};
|
||||
loanContainer = MemberIdService.AppendMemberInfo(loanContainer, token);
|
||||
loanContainer = ForwardedHeaderService.AppendIPAddress(loanContainer, HttpContext);
|
||||
|
||||
|
||||
loanApplicationStorage.CreateContainer(loanContainer);
|
||||
HttpContext.Response.StatusCode = 201;
|
||||
return new { id = loanContainer.Id, message = "Successfully saved document" };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use update a loan container
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a status after attempting to update a loan container with new info.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// PUT: api/Storage
|
||||
[HttpPut]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Put([FromBody] LoanContainer loanContainer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cosmosId = loanContainer?.Id;
|
||||
if (!String.IsNullOrEmpty(cosmosId))
|
||||
{
|
||||
var assertion = _tokenService.GetAuthenticatedDataFromCookie(HttpContext);
|
||||
loanContainer = CosmosIdService.AppendCosmosId(loanContainer, cosmosId);
|
||||
loanContainer = MemberIdService.AppendMemberInfo(loanContainer, assertion);
|
||||
loanContainer = ForwardedHeaderService.AppendIPAddress(loanContainer, HttpContext);
|
||||
|
||||
// Append files
|
||||
LoanContainer previousContainer = loanApplicationStorage.GetContainerById(cosmosId);
|
||||
if (previousContainer.LoanApplication != null) {
|
||||
loanContainer.LoanApplication.Files = previousContainer.LoanApplication.Files;
|
||||
}
|
||||
|
||||
loanApplicationStorage.UpdateContainer(loanContainer);
|
||||
return CreatedAtAction(nameof(Post), new { id = loanContainer.Id }, loanContainer);
|
||||
}
|
||||
return BadRequest("Loan container invalid.");
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
loanApplicationStorage.UpdateError(loanContainer.Id, new LoanError(LoanErrorCode.Error_Updating, e.Message));
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to delete a loan
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Deletes a loan given an id.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Requires JWT token from Azure AD, AND Admin role.
|
||||
/// </remarks>
|
||||
// DELETE: api/Storage/Loan/5
|
||||
[HttpDelete("Loan/{id}")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public IActionResult DeleteLoan(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(id))
|
||||
{
|
||||
return BadRequest("Id is required.");
|
||||
}
|
||||
loanApplicationStorage.DeleteApplication(id);
|
||||
loanApplicationStorage.UpdateStatus(id, LoanStatus.Deleted);
|
||||
return Ok(id);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
loanApplicationStorage.UpdateError(id, new LoanError(LoanErrorCode.Error_Deleting, e.Message));
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to delete an entire loan container
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Deletes a loan container given a container id.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Requires JWT token from Azure AD, AND Admin role.
|
||||
/// </remarks>
|
||||
// DELETE: api/Storage/LoanContainer/5
|
||||
[HttpDelete("LoanContainer/{id}")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public IActionResult DeleteContainer(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(id))
|
||||
{
|
||||
return BadRequest("Id is required.");
|
||||
}
|
||||
loanApplicationStorage.DeleteContainer(id);
|
||||
return Ok(id);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/Storage/Settings
|
||||
[HttpGet("Settings")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult GetSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(new { fileSizeLimit = _blobStorageSettings.MaxFileSizeInMB, fileLimit = _blobStorageSettings.FileLimit, allowedFileTypes = _blobStorageSettings.AllowedFileTypes });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/Storage/Documents
|
||||
[HttpGet("Documents/{cosmosLoanId}")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult GetDocuments(string cosmosLoanId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(cosmosLoanId))
|
||||
{
|
||||
return BadRequest(new { error = "Invalid or missing loan ID" });
|
||||
}
|
||||
|
||||
LoanContainer container = loanApplicationStorage.GetContainerById(cosmosLoanId);
|
||||
|
||||
if(container.LoanApplication == null)
|
||||
{
|
||||
return NotFound(new { loanId = cosmosLoanId });
|
||||
}
|
||||
|
||||
return Ok(new { files = container.LoanApplication.Files });
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
// POST: api/Storage/Documents/5
|
||||
[HttpPost("Documents/{cosmosLoanId}")]
|
||||
[ProducesResponseType(typeof(string), 201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult PostDocument(string cosmosLoanId, [FromForm] DocumentUploadFromLoanApplication request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(cosmosLoanId))
|
||||
{
|
||||
return BadRequest(new { error = "Invalid or missing loan ID" });
|
||||
}
|
||||
|
||||
LoanContainer container = loanApplicationStorage.GetContainerById(cosmosLoanId);
|
||||
|
||||
if (container.LoanApplication == null)
|
||||
{
|
||||
return NotFound(new { loanId = cosmosLoanId });
|
||||
}
|
||||
|
||||
if (DocumentUploadFromLoanApplication.IsNullOrEmpty(request))
|
||||
{
|
||||
return BadRequest(new { error = "Cannot upload empty file" });
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
// Check file type
|
||||
var filename = request.File.FileName.ToLower();
|
||||
|
||||
var validExtension = false;
|
||||
|
||||
foreach (string extension in _blobStorageSettings.AllowedFileTypes)
|
||||
{
|
||||
if (filename.EndsWith(extension))
|
||||
{
|
||||
validExtension = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!validExtension)
|
||||
{
|
||||
return BadRequest(new { error = "Invalid file type" });
|
||||
}
|
||||
|
||||
// Check file size
|
||||
if (request.File.Length > _blobStorageSettings.MaxFileSizeInMB * 1024 * 1024)
|
||||
{
|
||||
return BadRequest(new { error = $"File size exceeds {_blobStorageSettings.MaxFileSizeInMB} MB" });
|
||||
}
|
||||
|
||||
request.File.CopyTo(ms);
|
||||
|
||||
var fileBytes = ms.ToArray();
|
||||
|
||||
var fileId = Guid.NewGuid();
|
||||
|
||||
LoanApplicationFile file = new(cosmosLoanId, fileId, request.File.FileName, request.Section, request.SectionId, request.Description);
|
||||
|
||||
container.LoanApplication.Files.Add(file);
|
||||
|
||||
loanApplicationStorage.UpdateContainer(container);
|
||||
|
||||
_blobStorageAccess.SaveBlob(file, fileBytes);
|
||||
|
||||
return Created($"/api/Storage/Documents/{cosmosLoanId}/{fileId}", new { fileId });
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: api/Storage/Documents/1/5
|
||||
[HttpDelete("Documents/{cosmosLoanId}/{fileId}")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(typeof(string), 400)]
|
||||
[ProducesResponseType(typeof(string), 404)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult DeleteDocument(string cosmosLoanId, string fileId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(cosmosLoanId) || String.IsNullOrEmpty(fileId))
|
||||
{
|
||||
return BadRequest(new { error = "Invalid or missing loan or file ID" });
|
||||
}
|
||||
|
||||
LoanContainer container = loanApplicationStorage.GetContainerById(cosmosLoanId);
|
||||
|
||||
if (container.LoanApplication == null)
|
||||
{
|
||||
return NotFound(new { loanId = cosmosLoanId });
|
||||
}
|
||||
|
||||
foreach (LoanApplicationFile file in container.LoanApplication.Files)
|
||||
{
|
||||
if(file.FileId.ToString().Equals(fileId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_blobStorageAccess.DeleteBlob(file);
|
||||
|
||||
container.LoanApplication.Files.Remove(file);
|
||||
|
||||
loanApplicationStorage.UpdateContainer(container);
|
||||
|
||||
return Ok(new { message = "file deleted", fileId });
|
||||
}
|
||||
}
|
||||
|
||||
return NotFound(new { fileId });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: api/Storage/Documents/1/5
|
||||
[HttpPost("Documents/DeleteAll/{cosmosLoanId}")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(typeof(string), 400)]
|
||||
[ProducesResponseType(typeof(string), 404)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult DeleteDocuments(string cosmosLoanId, [FromForm] string files)
|
||||
{
|
||||
try
|
||||
{
|
||||
/*if (String.IsNullOrEmpty(cosmosLoanId) || String.IsNullOrEmpty(fileIds))
|
||||
{
|
||||
return BadRequest(new { error = "Invalid or missing loan or file ID" });
|
||||
}*/
|
||||
|
||||
LoanContainer container = loanApplicationStorage.GetContainerById(cosmosLoanId);
|
||||
|
||||
if (container.LoanApplication == null)
|
||||
{
|
||||
return NotFound(new { loanId = cosmosLoanId });
|
||||
}
|
||||
List<LoanApplicationFile> filesDeleted = new List<LoanApplicationFile>();
|
||||
foreach (LoanApplicationFile file in container.LoanApplication.Files)
|
||||
{
|
||||
|
||||
if (files.ToString().Contains(file.FileId.ToString()))
|
||||
{
|
||||
_blobStorageAccess.DeleteBlob(file);
|
||||
|
||||
filesDeleted.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (LoanApplicationFile file in filesDeleted)
|
||||
{
|
||||
container.LoanApplication.Files.Remove(file);
|
||||
}
|
||||
|
||||
loanApplicationStorage.UpdateContainer(container);
|
||||
|
||||
return Ok(new { message = "files deleted", filesDeleted });
|
||||
|
||||
/*return NotFound(new { fileId });*/
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("/GetLog")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
public IActionResult GetLog()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Error("Test log error from localhost");
|
||||
return Ok("Logging to Seq");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Application.Common.Domain;
|
||||
using Landmark.OLA.Application.DataAccess;
|
||||
|
||||
|
||||
namespace Landmark.OLA.Application.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class UiSettingsController : ControllerBase
|
||||
{
|
||||
private readonly UiSettings _uiSettings;
|
||||
private readonly IBranchDataAccess _branchStorage;
|
||||
private readonly IConfigurationAccess _configurationAccess;
|
||||
|
||||
public UiSettingsController(UiSettings uiSettings,
|
||||
IBranchDataAccess branchDataAccess,
|
||||
IConfigurationAccess configurationAccess)
|
||||
{
|
||||
_uiSettings = uiSettings;
|
||||
_branchStorage = branchDataAccess;
|
||||
_configurationAccess = configurationAccess;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to retrieve UI Settings
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns the UI Settings; with the dynamic branch mappings injected.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: None required or provided.
|
||||
/// </remarks>
|
||||
// GET: api/Content/
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(UiSettings), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = _uiSettings;
|
||||
settings.BranchMapping = _branchStorage.GetBranchConfig();
|
||||
settings.BranchNameMapping = _branchStorage.GetBranchNameConfig();
|
||||
settings.Configuration = _configurationAccess.GetStoredSettings();
|
||||
return Ok(settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to get stored config (run-time changeable)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns the current StoredConfig
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Requires Azure AD token and Admin role.
|
||||
/// </remarks>
|
||||
// GET: api/UiSettings/StoredSettings
|
||||
[HttpGet("StoredSettings", Name = "GetStoredSettings")]
|
||||
[ProducesResponseType(typeof(StoredSettings), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
//[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public IActionResult GetStoredSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = _configurationAccess.GetStoredSettings();
|
||||
return Ok(config);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to save the initial storedConfig.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a saved configuration with id. This call will fail if ANY configs currently exist.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Requires Azure AD Jwt token and admin role.
|
||||
/// </remarks>
|
||||
// POST: api/UiSettings/StoredSettings
|
||||
[HttpPost("StoredSettings", Name = "CreateStoredSettings")]
|
||||
[ProducesResponseType(typeof(string), 201)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(typeof(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary), 400)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public object CreateStoredSettings([FromBody] StoredSettings config)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (config != null)
|
||||
{
|
||||
config.Id = (Guid.NewGuid()).ToString();
|
||||
config.LastUpdated = DateTime.Now;
|
||||
_configurationAccess.CreateStoredSettings(config);
|
||||
|
||||
HttpContext.Response.StatusCode = 201;
|
||||
return new { id = config.Id, message = "Successfully saved configuration" };
|
||||
}
|
||||
return BadRequest("StoredConfig is required.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use update the StoredConfig
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a status after attempting to update the StoredConfig.
|
||||
/// Cookies: None required or set.
|
||||
/// Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
|
||||
/// </remarks>
|
||||
// PUT: api/UiSettings/StoredSettings
|
||||
[HttpPut("StoredSettings", Name = "UpdateStoredSettings")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(typeof(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary), 400)]
|
||||
[ProducesResponseType(403)]
|
||||
[ProducesResponseType(500)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
|
||||
public IActionResult UpdateStoredSettings([FromBody] StoredSettings config)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cosmosId = config?.Id;
|
||||
if (!String.IsNullOrEmpty(cosmosId))
|
||||
{
|
||||
config.LastUpdated = DateTime.Now;
|
||||
_configurationAccess.UpdateStoredSettings(config);
|
||||
return CreatedAtAction(nameof(UpdateStoredSettings), new { id = config.Id }, config);
|
||||
}
|
||||
return BadRequest("StoredSettings invalid.");
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Azure.Storage.Blobs;
|
||||
using Landmark.OLA.Application.Configuration;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public class BlobStorageAccess : IBlobStorageAccess
|
||||
{
|
||||
readonly BlobServiceClient _blobServiceClient;
|
||||
readonly string _containerName;
|
||||
readonly BlobContainerClient _containerClient;
|
||||
readonly BlobStorageConnectionSettings _settings;
|
||||
|
||||
public BlobStorageAccess(IOptions<BlobStorageConnectionSettings> settings)
|
||||
{
|
||||
_settings = settings.Value;
|
||||
|
||||
_blobServiceClient = new BlobServiceClient($"AccountName={_settings.AccountName};AccountKey={_settings.AccountKey}");
|
||||
_containerName = "documents";
|
||||
_containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
|
||||
}
|
||||
|
||||
public byte[] GetBlobAsBytes(LoanApplicationFile file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var blob = _containerClient.GetBlobClient(file.GetBlobPath());
|
||||
|
||||
var fileContent = blob.DownloadContent();
|
||||
var fileBytes = fileContent.Value.Content.ToArray();
|
||||
|
||||
return fileBytes;
|
||||
}
|
||||
|
||||
public string GetBlobAsBase64(LoanApplicationFile file)
|
||||
{
|
||||
var fileBytes = GetBlobAsBytes(file);
|
||||
|
||||
return Convert.ToBase64String(fileBytes);
|
||||
}
|
||||
|
||||
public bool SaveBlob(LoanApplicationFile file, byte[] fileBytes)
|
||||
{
|
||||
if(file == null || fileBytes == null || fileBytes.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BlobClient blobClient = _containerClient.GetBlobClient(file.GetBlobPath());
|
||||
blobClient.Upload(new BinaryData(fileBytes));
|
||||
blobClient.SetMetadata(file.GetMetadata());
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DeleteBlob(LoanApplicationFile file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BlobClient blobClient = _containerClient.GetBlobClient(file.GetBlobPath());
|
||||
return blobClient.DeleteIfExists();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using Serilog;
|
||||
using CRIF.ACTion.Configuration;
|
||||
using CRIF.ACTion.Services.Crif;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public class BranchDataAccess : IDisposable, IBranchDataAccess
|
||||
{
|
||||
private bool disposed = false;
|
||||
private protected readonly CrifSettings _crifSettings;
|
||||
private readonly IDataClient _client;
|
||||
private readonly ICrifVendorClient _vendorClient;
|
||||
private readonly string _collectionName = "Branches";
|
||||
private readonly string _branchNameCollectionName = "ServicingChannelToBranchName";
|
||||
private readonly List<BranchNameData> _branchNameDataListDefaults = new List<BranchNameData>
|
||||
{
|
||||
new() { ChannelId = 0, BranchName = "Not Found" },
|
||||
new() { ChannelId = 1, BranchName = "ALL" }
|
||||
};
|
||||
|
||||
public BranchDataAccess(
|
||||
IDataClient dataClient,
|
||||
IOptions<CrifSettings> crifSettings,
|
||||
ICrifVendorClient vendorClient)
|
||||
{
|
||||
_client = dataClient;
|
||||
_crifSettings = crifSettings.Value;
|
||||
_vendorClient = vendorClient;
|
||||
}
|
||||
|
||||
private IMongoCollection<BranchConfig> GetBranchContainersCollection()
|
||||
{
|
||||
return _client.GetDatabase().GetCollection<BranchConfig>(_collectionName);
|
||||
}
|
||||
|
||||
private IMongoCollection<BranchNameConfig> GetBranchNameContainersCollection()
|
||||
{
|
||||
return _client.GetDatabase().GetCollection<BranchNameConfig>(_branchNameCollectionName);
|
||||
}
|
||||
|
||||
public BranchConfig GetBranchConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetBranchContainersCollection();
|
||||
var result = collection.Find(new BsonDocument()).ToList().FirstOrDefault();
|
||||
return result ?? throw new Exception("Could not find branch mapping");
|
||||
}
|
||||
catch (MongoConnectionException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Could not load branch config");
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateBranchConfig(BranchConfig branchConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetBranchContainersCollection();
|
||||
|
||||
if (collection.Find(new BsonDocument()).ToList().Count > 0)
|
||||
{
|
||||
throw new Exception("Can only insert 1 branch config");
|
||||
}
|
||||
collection.InsertOne(branchConfig);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while creating branch config");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while creating branch config");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateBranchConfig(BranchConfig branchConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetBranchContainersCollection();
|
||||
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachLoan => eachLoan.Id == branchConfig.Id, branchConfig);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while creating updating branch config");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while creating updating branch config");
|
||||
}
|
||||
}
|
||||
|
||||
public BranchNameConfig GetBranchNameConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetBranchNameContainersCollection();
|
||||
var result = collection.Find(new BsonDocument()).ToList().FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
UpsertBranchNameConfig();
|
||||
}
|
||||
else if (result != null && result.BranchNamesByChannelId.Count <= _branchNameDataListDefaults.Count)
|
||||
{
|
||||
UpsertBranchNameConfig(true);
|
||||
}
|
||||
|
||||
collection = GetBranchNameContainersCollection();
|
||||
result = collection.Find(new BsonDocument()).ToList().FirstOrDefault();
|
||||
return result;
|
||||
}
|
||||
catch (MongoConnectionException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Could not load branch name config");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpsertBranchNameConfig(bool IsUpdate = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetBranchNameContainersCollection();
|
||||
|
||||
if (collection.Find(new BsonDocument()).ToList().Count > 0 && !IsUpdate)
|
||||
{
|
||||
throw new Exception("Can only insert 1 branch name config");
|
||||
}
|
||||
|
||||
SortedList<int, string> configurationDetails = _vendorClient.GetBranchNameConfiguration("getChannels").GetAwaiter().GetResult();
|
||||
BranchNameConfig branchNameConfig = new();
|
||||
|
||||
List<BranchNameData> branchNameDataList = CreateBranchNameDataList(configurationDetails);
|
||||
|
||||
branchNameConfig.Id = (Guid.NewGuid()).ToString();
|
||||
branchNameConfig.Date = DateTime.Now;
|
||||
branchNameConfig.BranchNamesByChannelId = branchNameDataList;
|
||||
|
||||
if (IsUpdate)
|
||||
{
|
||||
var oldConfig = collection.Find(new BsonDocument()).ToList().FirstOrDefault();
|
||||
branchNameConfig.Id = oldConfig.Id;
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachConfig => eachConfig.Id == branchNameConfig.Id, branchNameConfig);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
collection.InsertOne(branchNameConfig);
|
||||
}
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while creating branch names config");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while creating branch names config");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateBranchNameConfig()
|
||||
{
|
||||
UpsertBranchNameConfig(true);
|
||||
}
|
||||
|
||||
public List<BranchNameData> CreateBranchNameDataList(SortedList<int, string> configurationDetails)
|
||||
{
|
||||
List<BranchNameData> branchNameDataList = _branchNameDataListDefaults;
|
||||
|
||||
foreach (var branchNameData in configurationDetails)
|
||||
{
|
||||
try
|
||||
{
|
||||
branchNameDataList.Add(new BranchNameData { ChannelId = branchNameData.Key, BranchName = branchNameData.Value });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
return branchNameDataList;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
public int GetDistributionChannelByZipCode(string zipCode)
|
||||
{
|
||||
var config = GetBranchConfig();
|
||||
foreach (var eachBranch in config.BranchCodesByZip)
|
||||
{
|
||||
if (eachBranch.Zipcode == zipCode)
|
||||
{
|
||||
return eachBranch.ChannelId;
|
||||
}
|
||||
}
|
||||
return _crifSettings.ServicingChannels.DefaultServicingChannel;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Application.Common.Domain;
|
||||
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public class ConfigurationAccess : IDisposable, IConfigurationAccess
|
||||
{
|
||||
private bool disposed = false;
|
||||
private readonly IDataClient _client;
|
||||
private readonly string _collectionName = "AppConfiguration";
|
||||
|
||||
public ConfigurationAccess(
|
||||
IDataClient dataClient)
|
||||
{
|
||||
_client = dataClient;
|
||||
}
|
||||
|
||||
private IMongoCollection<StoredSettings> GetConfigurationContainersCollection()
|
||||
{
|
||||
return _client.GetDatabase().GetCollection<StoredSettings>(_collectionName);
|
||||
}
|
||||
|
||||
public StoredSettings GetStoredSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetConfigurationContainersCollection();
|
||||
var result = collection.Find(new BsonDocument()).ToList().FirstOrDefault();
|
||||
if( result == null )
|
||||
{
|
||||
throw new Exception("Could not find app configuration.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (MongoConnectionException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Could not load app configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateStoredSettings(StoredSettings storedSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetConfigurationContainersCollection();
|
||||
|
||||
if (collection.Find(new BsonDocument()).ToList().Count > 0)
|
||||
{
|
||||
throw new Exception("Can only insert 1 configuration");
|
||||
}
|
||||
collection.InsertOne(storedSettings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while creating app configuration");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateStoredSettings(StoredSettings storedSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetConfigurationContainersCollection();
|
||||
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachConfig => eachConfig.Id == storedSettings.Id, storedSettings);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while updating app configuration");
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public class DataAccessConnection
|
||||
{
|
||||
public string DatabaseName { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Security.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
// Reference: http://mongodb.github.io/mongo-csharp-driver/2.5
|
||||
public class DataClient : IDataClient
|
||||
{
|
||||
private readonly MongoClientSettings _mongoSettings;
|
||||
private readonly DataAccessConnection _configuration;
|
||||
private readonly MongoClient _client;
|
||||
private readonly IMongoDatabase _database;
|
||||
|
||||
public DataClient(IOptions<DataAccessConnection> settings) {
|
||||
_configuration = settings.Value;
|
||||
_mongoSettings = GetMongoSettings();
|
||||
_client = new MongoClient(_mongoSettings);
|
||||
_database = _client.GetDatabase(_configuration.DatabaseName);
|
||||
}
|
||||
|
||||
public IMongoDatabase GetDatabase()
|
||||
{
|
||||
return _database;
|
||||
}
|
||||
|
||||
private MongoClientSettings GetMongoSettings()
|
||||
{
|
||||
MongoClientSettings settings = new()
|
||||
{
|
||||
Server = new MongoServerAddress(_configuration.Host, _configuration.Port),
|
||||
UseTls = true,
|
||||
SslSettings = new SslSettings()
|
||||
};
|
||||
settings.SslSettings.EnabledSslProtocols = SslProtocols.Tls12;
|
||||
|
||||
MongoIdentity identity = new MongoInternalIdentity(_configuration.DatabaseName, _configuration.UserName);
|
||||
MongoIdentityEvidence evidence = new PasswordEvidence(_configuration.Password);
|
||||
|
||||
settings.Credential = new MongoCredential("SCRAM-SHA-1", identity, evidence);
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public interface IBlobStorageAccess
|
||||
{
|
||||
bool SaveBlob(LoanApplicationFile file, byte[] fileBytes);
|
||||
|
||||
byte[] GetBlobAsBytes(LoanApplicationFile file);
|
||||
|
||||
string GetBlobAsBase64(LoanApplicationFile file);
|
||||
|
||||
bool DeleteBlob(LoanApplicationFile file);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using CRIF.ACTion.Services.Landmark.DistributionChannel;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public interface IBranchDataAccess : IDistributionChannelLookupService
|
||||
{
|
||||
void Dispose();
|
||||
BranchConfig GetBranchConfig();
|
||||
void UpdateBranchConfig(BranchConfig branchConfig);
|
||||
void CreateBranchConfig(BranchConfig branchConfig);
|
||||
BranchNameConfig GetBranchNameConfig();
|
||||
void UpdateBranchNameConfig();
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using Landmark.OLA.Application.Common.Domain;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public interface IConfigurationAccess
|
||||
{
|
||||
void Dispose();
|
||||
StoredSettings GetStoredSettings();
|
||||
void UpdateStoredSettings(StoredSettings branchConfig);
|
||||
void CreateStoredSettings(StoredSettings branchConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public interface IDataClient
|
||||
{
|
||||
IMongoDatabase GetDatabase();
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.Loan.Enums;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccessLayer
|
||||
{
|
||||
public interface ILoanApplicationDataAccess
|
||||
{
|
||||
void CreateContainer(LoanContainer loanContainer);
|
||||
void DeleteApplication(string id);
|
||||
void DeleteContainer(string id);
|
||||
void Dispose();
|
||||
List<LoanContainer> GetLoanContainers(Filter loanFilter);
|
||||
List<LoanContainer> GetAllMemberContainers(string memberId, string individualId);
|
||||
LoanContainer GetContainerById(string guid);
|
||||
string GetContainerIdByLoanId(string loanId);
|
||||
void UpdateContainer(LoanContainer loanContainer);
|
||||
void UpdateStatus(string id, LoanStatus status);
|
||||
void UpdateError(string id, LoanError error);
|
||||
void UpdateLoanId(string id, string loanId);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Library.Internal.NotificationsModels.Models;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public interface INotificationDao
|
||||
{
|
||||
Task SendEmailAsync(EmailRequest request);
|
||||
}
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using Serilog;
|
||||
using Landmark.OLA.Application.Common.Security;
|
||||
using Landmark.OLA.Application.Configuration;
|
||||
using Landmark.OLA.Application.DataAccess;
|
||||
using Landmark.OLA.Application.Services.MicroApiServices;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.Loan.Enums;
|
||||
using LCU.Library.Internal.NotificationsModels.Models;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccessLayer
|
||||
{
|
||||
public class LoanApplicationDataAccess : IDisposable, ILoanApplicationDataAccess
|
||||
{
|
||||
private bool disposed = false;
|
||||
private readonly IDataClient _client;
|
||||
private readonly INotificationsApiService _notificationsApiService;
|
||||
private readonly string _collectionName = "Applications";
|
||||
private readonly NotificationsApiConfiguration _notificationsApiConfiguration;
|
||||
private readonly Services.Email.IEmailTemplateService _emailTemplateService;
|
||||
|
||||
public LoanApplicationDataAccess(
|
||||
IDataClient dataClient,
|
||||
INotificationsApiService notificationsApiService,
|
||||
IOptions<NotificationsApiConfiguration> notificationsApiConfiguration,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
Services.Email.IEmailTemplateService emailTemplateService)
|
||||
{
|
||||
_client = dataClient;
|
||||
_notificationsApiService = notificationsApiService;
|
||||
_notificationsApiConfiguration = notificationsApiConfiguration.Value;
|
||||
_emailTemplateService = emailTemplateService;
|
||||
}
|
||||
|
||||
|
||||
public LoanContainer GetContainerById(string guid)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
var foundItems = collection.Find(eachContainer => eachContainer.Id == guid);
|
||||
if (foundItems.CountDocuments() != 1)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
return foundItems.First();
|
||||
}
|
||||
catch (MongoConnectionException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetContainerIdByLoanId(string loanId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
var foundItems = collection.Find(eachContainer => eachContainer.LoanId == loanId);
|
||||
if (foundItems.CountDocuments() != 1)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
return foundItems.First().Id;
|
||||
}
|
||||
catch (MongoConnectionException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<LoanContainer> GetAllMemberContainers(string memberId, string individualId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
var foundItems = collection.Find(eachContainer => eachContainer.MemberId == memberId && eachContainer.IndividualId == individualId && eachContainer.LoanApplication != null).ToList();
|
||||
|
||||
foreach (var container in foundItems)
|
||||
{
|
||||
MemberIdService.RemoveMemberId(container);
|
||||
}
|
||||
return foundItems;
|
||||
}
|
||||
catch (MongoConnectionException)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public List<LoanContainer> GetAllContainers()
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
return collection.Find(new BsonDocument()).ToList();
|
||||
}
|
||||
catch (MongoConnectionException)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public List<LoanContainer> GetLoanContainers(Filter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection().Find(BuildFilter(filter)).Sort(BuildSorter(filter)).Limit(filter.Count).ToList();
|
||||
return collection;
|
||||
}
|
||||
catch (MongoConnectionException)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateContainer(LoanContainer loanContainer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
|
||||
collection.InsertOne(loanContainer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while creating container");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateContainer(LoanContainer loanContainer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachLoan => eachLoan.Id == loanContainer.Id, loanContainer);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while updating container");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while updating container");
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteApplication(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
|
||||
var foundItems = collection.Find(eachContainer => eachContainer.Id == id);
|
||||
if (foundItems.CountDocuments() != 1)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
LoanContainer loanContainer = foundItems.First();
|
||||
loanContainer.LoanApplication = null;
|
||||
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachContainer => eachContainer.Id == loanContainer.Id, loanContainer);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while deleting application");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while deleting application");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateStatus(string id, LoanStatus status)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
|
||||
var foundItems = collection.Find(eachContainer => eachContainer.Id == id);
|
||||
if (foundItems.CountDocuments() != 1)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
LoanContainer loanContainer = foundItems.First();
|
||||
loanContainer.Status = status;
|
||||
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachContainer => eachContainer.Id == loanContainer.Id, loanContainer);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
if (status == LoanStatus.Vendor_Error || status == LoanStatus.Application_Error)
|
||||
{
|
||||
var reportId = id;
|
||||
|
||||
if ( loanContainer.LoanId != null)
|
||||
{
|
||||
reportId = loanContainer.LoanId;
|
||||
}
|
||||
|
||||
var contentHtml = "There was a " + status + " in loan application: " + reportId + ". <br><br>";
|
||||
|
||||
var request = new EmailRequest()
|
||||
{
|
||||
Subject = "Vendor Error In Loan Application",
|
||||
Recipient = _notificationsApiConfiguration.ErrorRecipients,
|
||||
Body = _emailTemplateService.BuildBrandedBody(contentHtml)
|
||||
};
|
||||
|
||||
_notificationsApiService.SendEmailAsync(request);
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while updating status");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while updating status");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateError(string id, LoanError error)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
|
||||
var foundItems = collection.Find(eachContainer => eachContainer.Id == id);
|
||||
if (foundItems.CountDocuments() != 1)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
LoanContainer loanContainer = foundItems.First();
|
||||
loanContainer.Error = error;
|
||||
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachContainer => eachContainer.Id == loanContainer.Id, loanContainer);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while updating error");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while updating error");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLoanId(string id, string loanId)
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
try
|
||||
{
|
||||
var foundItems = collection.Find(eachContainer => eachContainer.Id == id);
|
||||
if (foundItems.CountDocuments() != 1)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
LoanContainer loanContainer = foundItems.First();
|
||||
loanContainer.LoanId = loanId;
|
||||
|
||||
ReplaceOneResult result = collection.ReplaceOne(eachContainer => eachContainer.Id == loanContainer.Id, loanContainer);
|
||||
if (result.MatchedCount == 0 || result.ModifiedCount == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while updating loan id");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while updating loan id");
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteContainer(string id)
|
||||
{
|
||||
var collection = GetLoanContainersCollection();
|
||||
try
|
||||
{
|
||||
DeleteResult result = collection.DeleteOne(_ => _.Id == id);
|
||||
if (result.DeletedCount != 1)
|
||||
{
|
||||
throw new KeyNotFoundException("Item not found");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException en)
|
||||
{
|
||||
Log.Error(en.Message);
|
||||
throw new KeyNotFoundException(en.Message);
|
||||
}
|
||||
catch (MongoException ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw new InvalidDataException("Error occured while deleting container");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
throw new InvalidDataException("Error occured while deleting container");
|
||||
}
|
||||
}
|
||||
|
||||
private IMongoCollection<LoanContainer> GetLoanContainersCollection()
|
||||
{
|
||||
return _client.GetDatabase().GetCollection<LoanContainer>(_collectionName);
|
||||
}
|
||||
|
||||
private static FilterDefinition<LoanContainer> BuildFilter(Filter filter)
|
||||
{
|
||||
DateTime sDate = filter.StartDate.ToUniversalTime();
|
||||
DateTime eDate = filter.EndDate.ToUniversalTime();
|
||||
var builder = Builders<LoanContainer>.Filter;
|
||||
var filterValue = builder.Gt("Date", sDate) & builder.Lt("Date", eDate);
|
||||
for (int i = 0; i < filter.FilterField.Length; i++)
|
||||
{
|
||||
var field = filter.FilterField[i];
|
||||
if (field.NotEqual)
|
||||
{
|
||||
filterValue = filterValue & builder.Ne(field.Field, field.FilterValue);
|
||||
}
|
||||
else if (field.Equal)
|
||||
{
|
||||
filterValue = filterValue & builder.Eq(field.Field, field.FilterValue);
|
||||
}
|
||||
else if (field.GreaterThan)
|
||||
{
|
||||
filterValue = filterValue & builder.Gt(field.Field, field.FilterValue);
|
||||
}
|
||||
else if (field.LessThan)
|
||||
{
|
||||
filterValue = filterValue & builder.Lt(field.Field, field.FilterValue);
|
||||
}
|
||||
}
|
||||
return filterValue;
|
||||
}
|
||||
|
||||
private static string BuildSorter(Filter filter)
|
||||
{
|
||||
return "{ " + filter.SortField + ": " + (filter.SortDesc ? "-1}" : "1}");
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Landmark.OLA.Application.Configuration;
|
||||
using LCU.Library.Internal.LcuHttpClient.HttpRequest;
|
||||
using LCU.Library.Internal.NotificationsModels.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Landmark.OLA.Application.DataAccess
|
||||
{
|
||||
public class NotificationDao : INotificationDao
|
||||
{
|
||||
private readonly NotificationsApiConfiguration _configuration;
|
||||
private readonly IPostRequest _postRequest;
|
||||
|
||||
public NotificationDao(IOptions<NotificationsApiConfiguration> configuration, IPostRequest postRequest)
|
||||
{
|
||||
_configuration = configuration.Value;
|
||||
_postRequest = postRequest;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(EmailRequest request)
|
||||
=> await _postRequest.Async(new Uri(_configuration.SendEmailEndpointUrl), request);
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
Binary file not shown.
+65
@@ -0,0 +1,65 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ApplicationIcon />
|
||||
<OutputType>Exe</OutputType>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Files\" />
|
||||
<Folder Include="wwwroot\" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.13.0" />
|
||||
<PackageReference Include="FluentValidation" Version="12.0.0" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||
<PackageReference Include="LCU.Library.Internal.LcuHttpClient" Version="2.0.1" />
|
||||
<PackageReference Include="LCU.Library.Internal.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.NotificationsModels" Version="1.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.21" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.21" />
|
||||
<PackageReference Include="Microsoft.Build" Version="17.8.43" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.21" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.24" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
|
||||
<PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.9.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Http" Version="5.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="5.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CRIF.ACTion\CRIF.ACTion.csproj" />
|
||||
<ProjectReference Include="..\Landmark.OLA.Models\Landmark.OLA.Models.csproj" />
|
||||
<ProjectReference Include="..\OnlineBankingAuth\OnlineBankingAuth.csproj" />
|
||||
<ProjectReference Include="..\USPS\USPS.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Files\Logo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Files\IMPORTANT TERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Files\What You Should Know About Home Equity Lines of Credit Revised 8-2022.pdf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Landmark.OLA.Application.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,286 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Landmark.OLA.Application</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.AddressValidationController.Post(Landmark.OLA.Models.Addresses.Address)">
|
||||
<summary>
|
||||
Use to correct a postal address
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a service corrected postal address, given a postal address.
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.BranchConfigController.Get">
|
||||
<summary>
|
||||
Use to get the branch configuration
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a full branch configuration.
|
||||
Cookies: None required or set.
|
||||
Security: None.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.BranchConfigController.Post(Landmark.OLA.Models.Loan.BranchConfig)">
|
||||
<summary>
|
||||
Use to save the initial branch config.
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a saved branch config with id. This call will fail if ANY configs currently exist.
|
||||
Cookies: None required or set.
|
||||
Security: Requires Azure AD Jwt token and admin role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.BranchConfigController.Put(Landmark.OLA.Models.Loan.BranchConfig)">
|
||||
<summary>
|
||||
Use update the branch config
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a status after attempting to update the branch configuration.
|
||||
Cookies: None required or set.
|
||||
Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.DecisioningController.Put(System.String,Landmark.OLA.Models.Decisioning.DecisioningDetails)">
|
||||
<summary>
|
||||
Use to start decisioning on a loan
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns status of decision start, on 202 you can begin requesting details.
|
||||
Cookies: Will expire existing cookies.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.DecisioningController.Get(System.String)">
|
||||
<summary>
|
||||
Use to get details of a decision
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a decision status, which may be empty if the decision is not ready.
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.LoanApplicationController.Post(System.String)">
|
||||
<summary>
|
||||
Use to transmit an existing loan application to CRIF Lending
|
||||
</summary>
|
||||
<remarks>
|
||||
Transmits a loan application to CRIF lending given a loan storage id (not a loan id).
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.LoanApplicationController.Post(Landmark.OLA.Models.Loan.LoanApplication)">
|
||||
<summary>
|
||||
Use to transmit a new loan application directly to CRIF Lending
|
||||
</summary>
|
||||
<remarks>
|
||||
Transmits a loan application directly to CRIF Lending given a valid loan application.
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.LoggingController.Post(Landmark.OLA.Models.ClientError)">
|
||||
<summary>
|
||||
Use to send something to the logger from the client
|
||||
</summary>
|
||||
<remarks>
|
||||
Logs a message from a client.
|
||||
Cookies: None required or set.
|
||||
Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.PdfController.Get(System.String)">
|
||||
<summary>
|
||||
Use to get the pdfs
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns bytes of PDF.
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.SAMLAssertionConsumerController.Post(OnlineBankingAuth.Parsers.SAMLMessaging)">
|
||||
<summary>
|
||||
Use to refer a user to the application with a valid SAML assertion
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns an application redirect, adding in a SAML assertion via a JWT in a secure http-only cookie.
|
||||
Cookies: Set a standard pair of authentication and authorization cookies.
|
||||
Security: Passed document must include a valid, signed SAML assertion, provides member authentication.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.SAMLAssertionConsumerController.Get">
|
||||
<summary>
|
||||
Use to create an empty cookie session
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a pair of empty authorization and authentication cookies.
|
||||
Cookies: Sets empty Authentication and Authorization cookies.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.SAMLAssertionConsumerController.PostAsync(Landmark.OLA.Models.OnlineBanking.OnlineBankingFormData)">
|
||||
<summary>
|
||||
Use to create a cookie session with credentials
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a pair of authorization and authentication cookies when provided with valid credentials.
|
||||
Cookies: Sets Authentication and Authorization cookies.
|
||||
Security: Require.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.SAMLAssertionConsumerController.PostIdentify(Landmark.OLA.Application.Controllers.OnlineIdentityFormData)">
|
||||
<summary>
|
||||
Use to identify a unique individual on the member account.
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns OK if an individual ID was added to the cookie.
|
||||
Cookies: Sets Individual ID in cookie.
|
||||
Security: Requires existing cookie, with member.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StatusController.GetRoot">
|
||||
<summary>
|
||||
Attempts to load the root of the API result in a web-app redirect.
|
||||
</summary>
|
||||
<remarks>
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StatusController.GetCoreTest(System.String)">
|
||||
<summary>
|
||||
Attempts to load a website and return the reponse.
|
||||
</summary>
|
||||
<remarks>
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StatusController.Get">
|
||||
<summary>
|
||||
Use to get the status of the api
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a simple object describing the build time, version, and environment of the running API.
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StorageController.Post(Landmark.OLA.Models.Loan.Filter)">
|
||||
<summary>
|
||||
Use to retrieve a set of Loan Containers by filter
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns an array of Loan Containers given a filter
|
||||
Cookies: None required or set.
|
||||
Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StorageController.Get(System.String)">
|
||||
<summary>
|
||||
Use to get a specific loan container
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a loan container given a loan id.
|
||||
Cookies: None required or set.
|
||||
Security: Must have a valid JWT token retrieved from Azure AD.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StorageController.GetMemberApplications">
|
||||
<summary>
|
||||
Use to get loan containers for an authenticated member
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a list of loan containers given a valid cookie with member id.
|
||||
Cookies: Requires authenticated cookie with member id.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StorageController.Post(Landmark.OLA.Models.Loan.LoanContainer)">
|
||||
<summary>
|
||||
Use to save a loan container
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a saved loan container with id and possibly member id
|
||||
Cookies: Authenticated member cookie optional, will cause id to be saved.
|
||||
Security: Currently creates authentication and authorization cookies.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StorageController.Put(Landmark.OLA.Models.Loan.LoanContainer)">
|
||||
<summary>
|
||||
Use update a loan container
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a status after attempting to update a loan container with new info.
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StorageController.DeleteLoan(System.String)">
|
||||
<summary>
|
||||
Use to delete a loan
|
||||
</summary>
|
||||
<remarks>
|
||||
Deletes a loan given an id.
|
||||
Cookies: None required or set.
|
||||
Security: Requires JWT token from Azure AD, AND Admin role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.StorageController.DeleteContainer(System.String)">
|
||||
<summary>
|
||||
Use to delete an entire loan container
|
||||
</summary>
|
||||
<remarks>
|
||||
Deletes a loan container given a container id.
|
||||
Cookies: None required or set.
|
||||
Security: Requires JWT token from Azure AD, AND Admin role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.UiSettingsController.Get">
|
||||
<summary>
|
||||
Use to retrieve UI Settings
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns the UI Settings; with the dynamic branch mappings injected.
|
||||
Cookies: None required or set.
|
||||
Security: None required or provided.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.UiSettingsController.GetStoredSettings">
|
||||
<summary>
|
||||
Use to get stored config (run-time changeable)
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns the current StoredConfig
|
||||
Cookies: None required or set.
|
||||
Security: Requires Azure AD token and Admin role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.UiSettingsController.CreateStoredSettings(Landmark.OLA.Application.Common.Domain.StoredSettings)">
|
||||
<summary>
|
||||
Use to save the initial storedConfig.
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a saved configuration with id. This call will fail if ANY configs currently exist.
|
||||
Cookies: None required or set.
|
||||
Security: Requires Azure AD Jwt token and admin role.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Landmark.OLA.Application.Controllers.UiSettingsController.UpdateStoredSettings(Landmark.OLA.Application.Common.Domain.StoredSettings)">
|
||||
<summary>
|
||||
Use update the StoredConfig
|
||||
</summary>
|
||||
<remarks>
|
||||
Returns a status after attempting to update the StoredConfig.
|
||||
Cookies: None required or set.
|
||||
Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
|
||||
</remarks>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
|
||||
namespace Landmark.OLA.Application
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
|
||||
.ConfigureAppConfiguration((context, builder) =>
|
||||
{
|
||||
var builtConfig = builder.Build();
|
||||
|
||||
builder.AddJsonFile("/app/config/environment-appsettings/appsettings.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/app/config/workload-appsettings/appsettings.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-primary-seq-key.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-usps-settings-username.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-application-storage-password.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-crif-settings-api-settings-username.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-security-settings-private-cert-azure-thumbprint.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-security-settings-public-cert-azure-thumbprint.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-security-settings-token-secret.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-security-settings-digital-insight-consumer-key.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-security-settings-digital-insight-consumer-secret.json", optional: true, reloadOnChange: true);
|
||||
builder.AddJsonFile("/vault/secrets/online-loan-app-api-blob-storage-connection-settings-account-key.json", optional: true, reloadOnChange: true);
|
||||
})
|
||||
.UseSerilog();
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:56932",
|
||||
"sslPort": 44384
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Landmark.OLA.Application": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Landmark.OLA.Application.Controllers;
|
||||
using Landmark.OLA.Models.Core;
|
||||
|
||||
namespace Landmark.OLA.Application.Services
|
||||
{
|
||||
public class CoreAccessService
|
||||
{
|
||||
public CoreMemberOwner FindMatchingOwner(OnlineIdentityFormData document, List<CoreMemberOwner> owners)
|
||||
{
|
||||
if (owners != null && owners.Count != 0 && document != null)
|
||||
{
|
||||
foreach (var owner in owners)
|
||||
{
|
||||
if (DoesOwnerMatch(owner, document))
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Expects birthdate in form YYYY-MM-DD and taxid in form ###-##-####
|
||||
// Expects dob in form MM/DD/YYYY and ssn in form #### (last 4)
|
||||
// Tries to match this owner and this document.
|
||||
public bool DoesOwnerMatch(CoreMemberOwner owner, OnlineIdentityFormData document)
|
||||
{
|
||||
if (string.IsNullOrEmpty(owner.BirthDate) || owner.TaxId == null || owner.TaxId.Length != 11)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (owner.TaxId.Substring(7) != document.SocialSecurityNumber)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
string[] formats = { "MM/dd/yyyy", "yyyy-MM-dd" };
|
||||
var dobEntered = DateTime.ParseExact(document.DateOfBirth, formats, new CultureInfo("en-US"), DateTimeStyles.None);
|
||||
var dobDesired = DateTime.ParseExact(owner.BirthDate, formats, new CultureInfo("en-US"), DateTimeStyles.None);
|
||||
return dobEntered == dobDesired;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using Newtonsoft.Json;
|
||||
using Landmark.OLA.Models.Core;
|
||||
using LCU.Library.Internal.LcuHttpClient.HttpRequest;
|
||||
|
||||
namespace Landmark.OLA.Application.Services
|
||||
{
|
||||
public class CoreService : ICoreService
|
||||
{
|
||||
private readonly IGetRequest _getRequest;
|
||||
|
||||
public CoreService(IGetRequest getRequest)
|
||||
{
|
||||
_getRequest = getRequest;
|
||||
}
|
||||
|
||||
public async Task<CoreIndividual> GetIndividual(string individualId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri($"core/individuals?userId={individualId}");
|
||||
var responseString = await _getRequest.StringResponseAsync(uri);
|
||||
return JsonConvert.DeserializeObject<CoreIndividual>(responseString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure to get Individual for IndividualId: {individualId} in {GetType().Name}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace Landmark.OLA.Application.Services
|
||||
{
|
||||
public class CoreSettings
|
||||
{
|
||||
public string EndPoint { get; set; }
|
||||
public int Timeout { get; set; }
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Serilog;
|
||||
|
||||
namespace Landmark.OLA.Application.Services.Email
|
||||
{
|
||||
public class EmailTemplateService : IEmailTemplateService
|
||||
{
|
||||
private readonly string _embeddedLogoBase64;
|
||||
|
||||
public EmailTemplateService(IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_embeddedLogoBase64 = LoadEmbeddedLogo(webHostEnvironment);
|
||||
}
|
||||
|
||||
private static string LoadEmbeddedLogo(IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
if (webHostEnvironment == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var logoPath = Path.Combine(webHostEnvironment.ContentRootPath, "Files", "Logo.png");
|
||||
if (!File.Exists(logoPath))
|
||||
{
|
||||
Log.Warning("Logo not found at {LogoPath}. Email will fall back to remote image.", logoPath);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var bytes = File.ReadAllBytes(logoPath);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to embed logo from file.");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string BuildBrandedBody(string contentHtml, string redBannerMessage = "")
|
||||
{
|
||||
var topLogoTag = $"<img src=\"data:image/png;base64,{_embeddedLogoBase64}\" width=\"225\" height=\"62\" style=\"width: 225px; height: 62px\">";
|
||||
var bottomLogoTag = $"<img src=\"data:image/png;base64,{_embeddedLogoBase64}\" alt=\"Landmark Credit Union Logo\" height=\"37\" width=\"135\" style=\"width: 180px; max-height: 50px;\">";
|
||||
|
||||
redBannerMessage ??= string.Empty;
|
||||
var bannerHtml = string.IsNullOrWhiteSpace(redBannerMessage)
|
||||
? string.Empty
|
||||
:
|
||||
"<div class=\"red-banner\">" +
|
||||
"<span style=\"background-color: #FF0000; color: #FFFFFF; font-weight: bold; display: inline-block; padding: 4px 8px;\">" +
|
||||
redBannerMessage +
|
||||
"</span><br><br>" +
|
||||
"</div>";
|
||||
|
||||
var requestBody =
|
||||
"<html>" +
|
||||
"<body>" +
|
||||
"<table align=\"center\" width=\"87%\" style=\"margin: auto;\">" +
|
||||
"<tbody>" +
|
||||
"<tr height=\"50\"></tr>" +
|
||||
"<tr>" +
|
||||
"<td align=\"center\" style=\"margin: auto; width: 250px\">" +
|
||||
topLogoTag +
|
||||
"</td>" +
|
||||
"</tr>" +
|
||||
"<tr height=\"30\"></tr>" +
|
||||
"</tbody>" +
|
||||
"</table>" +
|
||||
"<table align=\"center\" width=\"87%\" style=\"margin: auto; color: #005674\">" +
|
||||
"<tbody>" +
|
||||
"<tr>" +
|
||||
"<td style=\"font-size: 75%; font-family: Verdana; padding-top: 10px;\">" +
|
||||
bannerHtml +
|
||||
contentHtml +
|
||||
"</td>" +
|
||||
"</tr>" +
|
||||
"<tr height=\"50\"></tr>" +
|
||||
"</tbody>" +
|
||||
"</table>" +
|
||||
"<table style=\"text-align: center; color: #888; mso-color-alt: #888; font-size: 70%; background-color: #eee; width: 100%; position: absolute; bottom: 0; margin: auto;\" align=\"center\">" +
|
||||
"<tbody>" +
|
||||
"<tr>" +
|
||||
"<td height=\"15\" colspan=\"4\"> </td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td height=\"100\" width=\"6.5%\"> </td>" +
|
||||
"<td height=\"100\" width=\"15%\">" +
|
||||
"<div style=\"padding-right: 15px;\">" +
|
||||
"<div style=\"margin: auto; display: flex; justify-content: center; align-items: center;\">" +
|
||||
bottomLogoTag +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"</td>" +
|
||||
"<td align=\"left\" style=\"padding-left: 25px; font-style: italic;\">" +
|
||||
"</td>" +
|
||||
"<td width=\"6.5%\"> </td>" +
|
||||
"</tr>" +
|
||||
"<tr>" +
|
||||
"<td height=\"15\" colspan=\"4\"> </td>" +
|
||||
"</tr>" +
|
||||
"</tbody>" +
|
||||
"</table>" +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
|
||||
return requestBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Landmark.OLA.Application.Services.Email
|
||||
{
|
||||
public interface IEmailTemplateService
|
||||
{
|
||||
string BuildBrandedBody(string contentHtml, string redBannerMessage = "");
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Landmark.OLA.Application.Services.Exceptions
|
||||
{
|
||||
public class CoreUnavailableException : Exception
|
||||
{
|
||||
public CoreUnavailableException(HttpRequestException hre): base(hre.Message, hre)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Landmark.OLA.Models.Core;
|
||||
|
||||
namespace Landmark.OLA.Application.Services
|
||||
{
|
||||
public interface ICoreService
|
||||
{
|
||||
Task<CoreIndividual> GetIndividual(string individualId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace Landmark.OLA.Application.Services
|
||||
{
|
||||
public interface IPdfService
|
||||
{
|
||||
byte[] GetFileBytes(string path);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Library.Internal.NotificationsModels.Models;
|
||||
|
||||
namespace Landmark.OLA.Application.Services.MicroApiServices
|
||||
{
|
||||
public interface INotificationsApiService
|
||||
{
|
||||
Task SendEmailAsync(EmailRequest request);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Landmark.OLA.Application.DataAccess;
|
||||
using LCU.Library.Internal.NotificationsModels.Models;
|
||||
|
||||
namespace Landmark.OLA.Application.Services.MicroApiServices
|
||||
{
|
||||
public class NotificationsApiService : INotificationsApiService
|
||||
{
|
||||
private readonly INotificationDao _notificationsApiDao;
|
||||
|
||||
public NotificationsApiService(INotificationDao notificationsApiDao)
|
||||
{
|
||||
_notificationsApiDao = notificationsApiDao;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(EmailRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _notificationsApiDao.SendEmailAsync(request);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var error = $"Failure during SendEmailAsync. Exception Message: {exception.Message}";
|
||||
Log.Error(error);
|
||||
throw new Exception(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Landmark.OLA.Application.Services
|
||||
{
|
||||
public class PdfService : IPdfService
|
||||
{
|
||||
public byte[] GetFileBytes(string path)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
||||
{
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Path {path} must exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Serilog;
|
||||
using CRIF.ACTion.Common.Parsers;
|
||||
using CRIF.ACTion.Configuration;
|
||||
using CRIF.ACTion.Decisioning;
|
||||
using CRIF.ACTion.Loan;
|
||||
using CRIF.ACTion.Loan.Individuals;
|
||||
using CRIF.ACTion.Services.Crif;
|
||||
using Landmark.OLA.Application.Common.Domain;
|
||||
using Landmark.OLA.Application.Common.Security;
|
||||
using Landmark.OLA.Application.Configuration;
|
||||
using Landmark.OLA.Application.DataAccess;
|
||||
using Landmark.OLA.Application.DataAccessLayer;
|
||||
using Landmark.OLA.Application.Services;
|
||||
using Landmark.OLA.Models;
|
||||
using Landmark.OLA.Models.Addresses;
|
||||
using Landmark.OLA.Models.Decisioning;
|
||||
using Landmark.OLA.Models.Decisioning.Enums;
|
||||
using Landmark.OLA.Models.Individuals;
|
||||
using Landmark.OLA.Models.Loan;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
using Landmark.OLA.Models.Vendor;
|
||||
using OnlineBankingAuth.Communication;
|
||||
using OnlineBankingAuth.Parsers;
|
||||
using OnlineBankingAuth.Services;
|
||||
using OnlineBankingAuth.Tokens;
|
||||
using LCU.Library.Internal.Logging;
|
||||
using LCU.Library.Internal.Logging.Middleware;
|
||||
using LCU.Library.Internal.Logging.Models;
|
||||
using LCU.Library.Internal.LcuHttpClient;
|
||||
using Landmark.OLA.Application.Services.MicroApiServices;
|
||||
using Landmark.OLA.Application.Services.Email;
|
||||
|
||||
namespace Landmark.OLA.Application
|
||||
{
|
||||
public class Startup(IConfiguration configuration)
|
||||
{
|
||||
public IConfiguration Configuration { get; } = configuration;
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var appConfig = new ApplicationSettings();
|
||||
Configuration.GetSection("Primary").Bind(appConfig);
|
||||
|
||||
services.AddSingleton(appConfig);
|
||||
|
||||
var loggerConfiguration = new LoggingConfiguration(appConfig.AppName,
|
||||
appConfig.SeqKey,
|
||||
appConfig.SeqUrl);
|
||||
var lcuLogging = new LcuLogging(loggerConfiguration);
|
||||
lcuLogging.UseLcuLogging();
|
||||
|
||||
Log.Information($"{appConfig.AppName} Starting Up");
|
||||
|
||||
services.AddLCUHttpServices();
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("DevCors", b => b
|
||||
.SetIsOriginAllowed(_ => true)
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials());
|
||||
|
||||
options.AddPolicy("ProdCors", b => b
|
||||
.WithOrigins(
|
||||
"https://applydev.landmarkcu.com",
|
||||
"https://applyqa.landmarkcu.com",
|
||||
"https://apply.landmarkcu.com",
|
||||
"https://qa-online-loan-app.apps.azure.lcu-internal.com",
|
||||
"https://uat-online-loan-app.apps.azure.lcu-internal.com",
|
||||
"https://prd-online-loan-app.apps.azure.lcu-internal.com",
|
||||
"https://qa-apps.landmarkcu.com",
|
||||
"https://uat-apps.landmarkcu.com",
|
||||
"https://apps.landmarkcu.com")
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials());
|
||||
});
|
||||
|
||||
services.AddControllers().AddNewtonsoftJson(o => o.SerializerSettings.Converters.Add(new StringEnumConverter()));
|
||||
|
||||
services.Configure<CookiePolicyOptions>(options =>
|
||||
{
|
||||
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
|
||||
options.CheckConsentNeeded = context => false;
|
||||
options.MinimumSameSitePolicy = SameSiteMode.None;
|
||||
});
|
||||
|
||||
//TODO #245: Disable in production?
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Landmark Loan API", Version = "v1" });
|
||||
// For this to work, I also had to check the generate xml box in build options.
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
c.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
|
||||
});
|
||||
|
||||
|
||||
services.AddScoped<ICrifVendorClient, CrifVendorClient>();
|
||||
services.AddScoped<IStatus, Status>();
|
||||
|
||||
services.Configure<DataAccessConnection>(options =>
|
||||
Configuration.GetSection("ApplicationStorage").Bind(options));
|
||||
services.AddSingleton<IDataClient, DataClient>();
|
||||
services.AddScoped<ILoanApplicationDataAccess, LoanApplicationDataAccess>();
|
||||
services.AddScoped<INotificationDao, NotificationDao>();
|
||||
services.AddScoped<IBranchDataAccess, BranchDataAccess>();
|
||||
services.AddScoped<IConfigurationAccess, ConfigurationAccess>();
|
||||
|
||||
services.AddScoped<ICrifService, CrifService>();
|
||||
services.AddScoped<IWcfInspectorBehavior, WcfInspectorBehavior>();
|
||||
|
||||
services.AddScoped<IResponseParser<Individual>, AddIndividualResponseParser>();
|
||||
services.AddScoped<IResponseParser<LoanApplication>, LoanApplicationResponseParser>();
|
||||
services.AddScoped<IResponseParser<DecisioningState>, DecisioningStateResponseParser>();
|
||||
services.AddScoped<IResponseParser<DecisioningDetails>, GetApplicationProductsResponseParser>();
|
||||
services.AddScoped<IResponseParser<SortedList<int, string>>, ConfigurationRequestResponseParser>();
|
||||
services.AddScoped<IResponseParser<Dictionary<string, string>>, ProductDetailsResponseParser>();
|
||||
services.AddScoped<IResponseParser<CustomFieldConfigs>, CustomFieldRequestResponseParser>();
|
||||
services.AddScoped<IResponseParser<DealershipAuthRequest>, DealershipResponseParser>();
|
||||
|
||||
services.AddSingleton<UiSettings>();
|
||||
|
||||
// Add USPS scopes
|
||||
services.AddScoped<IUspsVendorClient, USPS.Communication.UspsVendorClient>();
|
||||
services.AddScoped<USPS.Communication.IUspsService, USPS.Communication.UspsService>();
|
||||
|
||||
services.AddScoped<USPS.Parsers.IResponseParser<Address>, USPS.Parsers.AddressValidationResponseParser>();
|
||||
services.Configure<ApplicationControlConfiguration>(options => Configuration.GetSection("ApplicationControl").Bind(options));
|
||||
|
||||
services.Configure<CrifSettings>(options =>
|
||||
Configuration.GetSection("CrifSettings").Bind(options));
|
||||
|
||||
services.Configure<BlobStorageSettings>(options =>
|
||||
Configuration.GetSection("BlobStorageSettings").Bind(options));
|
||||
|
||||
services.Configure<BlobStorageConnectionSettings>(options =>
|
||||
Configuration.GetSection("BlobStorageConnectionSettings").Bind(options));
|
||||
|
||||
services.Configure<DealershipEmailSettings>(options =>
|
||||
Configuration.GetSection("DealershipEmailSettings").Bind(options));
|
||||
|
||||
services.AddSingleton<IBlobStorageAccess, BlobStorageAccess>();
|
||||
|
||||
// Set up USPS
|
||||
services.Configure<USPS.Configuration.UspsSettings>(options =>
|
||||
Configuration.GetSection("UspsSettings").Bind(options));
|
||||
|
||||
// Setup up Email
|
||||
services.Configure<NotificationsApiConfiguration>(options =>
|
||||
Configuration.GetSection("NotificationsApiConfiguration").Bind(options));
|
||||
services.AddScoped<INotificationsApiService, NotificationsApiService>();
|
||||
services.AddSingleton<IEmailTemplateService, EmailTemplateService>();
|
||||
|
||||
// Setup up Core
|
||||
var coreSettings = new CoreSettings();
|
||||
Configuration.GetSection("CoreSettings").Bind(coreSettings);
|
||||
services.AddSingleton(coreSettings);
|
||||
services.AddScoped<ICoreService, CoreService>();
|
||||
services.AddScoped<IPdfService, PdfService>();
|
||||
|
||||
// Set up SAML IdP scopes
|
||||
services.AddScoped<ITokenService, TokenService>();
|
||||
services.AddScoped<ISAMLParser<DISAMLAssertion>, SAMLParser>();
|
||||
|
||||
// Set up SAML IdP configuration
|
||||
services.Configure<OnlineBankingAuth.Configuration.SecuritySettings>(options =>
|
||||
{
|
||||
Configuration.GetSection("SecuritySettings").Bind(options);
|
||||
});
|
||||
|
||||
// Setup DI service
|
||||
services.AddScoped<IDigitalInsightVendorClient, DigitalInsightVendorClient>();
|
||||
services.AddScoped<IDigitalInsightService, DigitalInsightService>();
|
||||
services.AddScoped<IAuthTokenParser, AuthTokenParser>();
|
||||
|
||||
//Set up Authentication
|
||||
services
|
||||
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
// AzureAD config
|
||||
.AddAzureAdBearer(options => Configuration.Bind("AzureActiveDirectory", options));
|
||||
|
||||
services.AddAuthorization();
|
||||
services.AddDistributedMemoryCache();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationSettings appSettings)
|
||||
{
|
||||
Log.Information("Configuring...");
|
||||
|
||||
var policyCollection = new HeaderPolicyCollection()
|
||||
.AddDefaultSecurityHeaders()
|
||||
.AddCustomHeader("Cache-Control", "no-cache")
|
||||
.AddCustomHeader("Arr-Disable-Session-Affinity", "true");
|
||||
|
||||
app.UseCookiePolicy(new CookiePolicyOptions
|
||||
{
|
||||
CheckConsentNeeded = context => false,
|
||||
MinimumSameSitePolicy = SameSiteMode.None
|
||||
});
|
||||
|
||||
|
||||
app.UseSecurityHeaders(policyCollection);
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Landmark Loan API"));
|
||||
|
||||
app.UseMiddleware<RequestResponseLoggingMiddleware>();
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
|
||||
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")?.Equals("Development", StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
app.UseCors("DevCors");
|
||||
else
|
||||
app.UseCors("ProdCors");
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints => endpoints.MapControllers());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Landmark.OLA.Application
|
||||
{
|
||||
public interface IStatus
|
||||
{
|
||||
string Application { get; }
|
||||
DateTime CurrentDate { get; }
|
||||
string BuildVersion { get; }
|
||||
DateTime BuildDate { get; }
|
||||
string Environment { get; }
|
||||
}
|
||||
|
||||
public class Status : IStatus
|
||||
{
|
||||
public string Application => _env.ApplicationName;
|
||||
public DateTime CurrentDate => DateTime.Now;
|
||||
public string BuildVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
public DateTime BuildDate => AssemblyBuildDate();
|
||||
public string Environment => _env.EnvironmentName;
|
||||
|
||||
private readonly IWebHostEnvironment _env;
|
||||
|
||||
public Status(IWebHostEnvironment env)
|
||||
{
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public static DateTime AssemblyBuildDate()
|
||||
{
|
||||
//https://stackoverflow.com/a/804310/266594
|
||||
//TODO #246: Look up this value at start up to avoid hitting the filesystem repeatedly
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
System.IO.FileInfo fileInfo = new(assembly.Location);
|
||||
DateTime lastModified = fileInfo.LastWriteTime;
|
||||
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"Primary": {
|
||||
"AppName": "Loan API",
|
||||
"SeqUrl": "http://localhost:5080/",
|
||||
"SeqKey": "VALUE-IN-KEYVAULT",
|
||||
"LogFilePath": "serilog.log"
|
||||
},
|
||||
"AzureActiveDirectory": {
|
||||
"Instance": "https://login.microsoftonline.com/",
|
||||
"Domain": "landmarkcu.com",
|
||||
"TenantId": "63cd1554-2783-4a86-ae50-b9e6c21c9499",
|
||||
/** the azure id of the backend **/
|
||||
"ClientId": "f8173aad-fccb-4226-8c6a-963693956ad4",
|
||||
/** the azure id of the front end **/
|
||||
"ValidAudience": "59d54f39-3ff9-4166-8b16-254c00416754"
|
||||
},
|
||||
"ApplicationControl": {
|
||||
"NotifyLogging": true,
|
||||
"HomePageUrl": "http://localhost:4200",
|
||||
"ImportantTermsPdfFilePath": "./Files/IMPORTANT TERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf",
|
||||
"WhatYouShouldKnowPdfFilePath": "./Files/What You Should Know About Home Equity Lines of Credit Revised 8-2022.pdf"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"UspsSettings": {
|
||||
"Username": "VALUE-IN-KEYVAULT",
|
||||
"Password": "No Password",
|
||||
"EndpointUrl": "http://production.shippingapis.com/ShippingAPI.dll?"
|
||||
},
|
||||
"ApplicationStorage": {
|
||||
"DatabaseName": "LoanApplications",
|
||||
"UserName": "loan-app-dvl",
|
||||
"Host": "loan-app-dvl.documents.azure.com",
|
||||
"Port": 10255,
|
||||
"Password": "VALUE-IN-KEYVAULT"
|
||||
},
|
||||
"CrifSettings": {
|
||||
"ApiSettings": {
|
||||
"Username": "VALUE-IN-KEYVAULT",
|
||||
"InterfaceId": 600002,
|
||||
"OrgCode": "LandmarkCU",
|
||||
"OrgId": 4,
|
||||
"EndpointUrl": "https://www-lc3cst.myappro.com/ACS/ConnectorService.svc/ConnectorService"
|
||||
},
|
||||
"SourceChannels": {
|
||||
"DefaultSourceChannelId": 2399724,
|
||||
"CreditCardSourceChannelId": 2399734
|
||||
},
|
||||
"ProductIds": {
|
||||
"CC_CashBack": 132,
|
||||
"CC_Rewards": 133,
|
||||
"CC_Student": 134,
|
||||
"CC_Secure": 136,
|
||||
"EQ_HELOC_Flex": 139,
|
||||
"EQ_Home_Equity_Fixed": 148,
|
||||
"EQ_Mtg_Refi_Express": 149,
|
||||
"EQ_Mfg_Home_And_Land": 146,
|
||||
"V_Vehicle": 125,
|
||||
"V_VehiclePreapproval": 126,
|
||||
"M_Boat": 129,
|
||||
"P_Powersport": 131,
|
||||
"T_TrailerMotorhome": 130,
|
||||
"S_Signature": 140
|
||||
},
|
||||
"ServicingChannels": {
|
||||
"DefaultServicingChannel": 1698432
|
||||
},
|
||||
"CustomFieldMappings": {
|
||||
"LoanTypeToPromoId": {
|
||||
"132": "-114",
|
||||
"133": "-114",
|
||||
"134": "-114",
|
||||
"136": "-114",
|
||||
"139": "-118",
|
||||
"148": "-118",
|
||||
"149": "-118",
|
||||
"146": "-118",
|
||||
"125": "-117",
|
||||
"126": "-117",
|
||||
"129": "-117",
|
||||
"131": "-117",
|
||||
"130": "-117",
|
||||
"140": "-115"
|
||||
},
|
||||
"CustomFieldID": {
|
||||
"LicensePlateNumber": "-76",
|
||||
"CountyTitled": "-96",
|
||||
"MunicipalityType": "-173",
|
||||
"MunicipalityWhereKept": "-174"
|
||||
},
|
||||
"MunicipalityTypeCode": {
|
||||
"CITY": "402",
|
||||
"TOWN": "403",
|
||||
"VILLAGE": "404"
|
||||
}
|
||||
},
|
||||
"FormatCategoryMappings": {
|
||||
"Categories": {
|
||||
"Stip Verification": "-4",
|
||||
"Loan Application": "-3",
|
||||
"Vehicle Value": "-2",
|
||||
"Proof of Income": "-1",
|
||||
"Attachment": "1",
|
||||
"Appraisal": "12",
|
||||
"Flood": "13",
|
||||
"Title": "14",
|
||||
"eDocs": "15",
|
||||
"eCon": "16"
|
||||
},
|
||||
"DocumentToCategory": {
|
||||
"Proof of Income": "Proof of Income",
|
||||
"Boat Title": "Title",
|
||||
"Trailer Title": "Title",
|
||||
"Copy of the homeowners insurance": "Attachment",
|
||||
"Proof of being a student": "Attachment",
|
||||
"Copy of first mortgage balance statement": "Attachment",
|
||||
"DNR Registration Card": "Attachment",
|
||||
"Purchase Contract": "Attachment",
|
||||
"Manufactured Home Title / Purchase Contract": "Attachment"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SecuritySettings": {
|
||||
"PrivateCertAzureThumbprint": "VALUE-IN-KEYVAULT",
|
||||
"PublicCertAzureThumbprint": "VALUE-IN-KEYVAULT",
|
||||
"TokenSecret": "VALUE-IN-KEYVAULT",
|
||||
"SAMLSuccessRedirectRoute": "http://localhost:4200/select-loan?referrer=NCR",
|
||||
"SAMLFailureRedirectRoute": "http://localhost:4200/error?referrer=NCR",
|
||||
"DigitalInsightPostTokenURI": "https://diapis.stg1.digitalinsight.com/v1/oauth/token",
|
||||
"DigitalInsightConsumerKey": "VALUE-IN-KEYVAULT",
|
||||
"DigitalInsightConsumerSecret": "VALUE-IN-KEYVAULT",
|
||||
"DigitalInsightFIID": "03456"
|
||||
},
|
||||
"NotificationsApiConfiguration": {
|
||||
"Url": "http://localhost:50353",
|
||||
"SendEmail": "/api/Email",
|
||||
"ErrorRecipients": [ "[email protected]" ],
|
||||
"RetryCount": 5
|
||||
},
|
||||
"CoreSettings": {
|
||||
"EndPoint": "http://localhost:44355/",
|
||||
"Timeout": 60
|
||||
},
|
||||
"BlobStorageSettings": {
|
||||
"MaxFileSizeInMB": "10.0",
|
||||
"AllowedFileTypes": [ "gif", "jpg", "jpeg", "png", "pdf" ],
|
||||
"FileLimit": "5"
|
||||
},
|
||||
"BlobStorageConnectionSettings": {
|
||||
"AccountName": "devloanappsa",
|
||||
"AccountKey": "VALUE-IN-KEYVAULT"
|
||||
},
|
||||
"DealershipEmailSettings": {
|
||||
"RedBannerMessage": "This is from the QA test environment.",
|
||||
"Recipients": [ "[email protected]" ],
|
||||
"ActionLink": "https://actiona6t.criflending.com/Core/Application/AppSecurity.aspx?orgCode=landmarkcu&resID=&resTypeID=-995&appID=",
|
||||
"EmailAPIUrl": "https://localhost:50353/api/Email"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user