Files
2026-03-11 11:18:25 -05:00

578 lines
21 KiB
C#

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);
}
}
}
}