adding
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Configurations;
|
||||
|
||||
public class ApiConfiguration
|
||||
{
|
||||
public string AppName { get; set; }
|
||||
public string SeqUrl { get; set; }
|
||||
public string SeqApiKey { get; set; }
|
||||
public int FileUploadCutoffHours { get; set; }
|
||||
public string[] CorsAllowedOrigins { get; set; }
|
||||
public List<string> MaliciousFileFoundEmailList { get; set; }
|
||||
public bool IsAksDeploy { get; set; }
|
||||
}
|
||||
+588
@@ -0,0 +1,588 @@
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.Library.Internal.ApiSecurity.Attributes;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace LCU.API.Internal.Document.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class DocumentController : ControllerBase
|
||||
{
|
||||
private readonly IDocumentService _documentService;
|
||||
private readonly IDocumentAuditService _documentAuditService;
|
||||
private readonly List<DocumentAction> _allowedExternalActions = new()
|
||||
{
|
||||
DocumentAction.Download
|
||||
};
|
||||
|
||||
public DocumentController(IDocumentService documentService,
|
||||
IDocumentAuditService documentAuditService)
|
||||
{
|
||||
_documentService = documentService;
|
||||
_documentAuditService = documentAuditService;
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentResponse), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentById(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocument(documentId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("{personOrOrgNumber:long}/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentResponse), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetMemberDocumentById(long personOrOrgNumber, Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocument(personOrOrgNumber, documentId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Obsolete]
|
||||
[HttpGet("GetDocumentByBlobId/{documentBlobId:guid}")]
|
||||
[ProducesResponseType(typeof(byte[]), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentByBlobId(Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentByBlobId(documentBlobId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentBlobId:{documentBlobId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("Content/{documentBlobId:guid}")]
|
||||
[ProducesResponseType(typeof(byte[]), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentContentByBlobId(Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentByBlobId(documentBlobId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentBlobId:{documentBlobId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("{personOrOrgNumber:long}/Content/{documentBlobId:guid}")]
|
||||
[ProducesResponseType(typeof(byte[]), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetMemberDocumentContentByBlobId(long personOrOrgNumber, Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentByBlobId(personOrOrgNumber, documentBlobId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentBlobId:{documentBlobId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost("Search")]
|
||||
[ProducesResponseType(typeof(List<DocumentInformationResponse>),200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> PostDocumentInformationByFilter([FromBody] DocumentInformationFilteredSearch documentInformationFilteredSearch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.SearchDocumentInformation(documentInformationFilteredSearch.Filter, documentInformationFilteredSearch.Skip, documentInformationFilteredSearch.Take, documentInformationFilteredSearch.SortDefinition);
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("AuditInformation/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(List<DocumentAuditResponse>),200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentAuditInformationById(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentAuditService.GetDocumentAudits(documentId);
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("AuditInformation")]
|
||||
[ProducesResponseType(typeof(List<DocumentAuditResponse>), 200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentAuditInformationByIds([FromQuery]Guid[] documentIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentAuditService.GetDocumentAudits(documentIds);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPost("{documentId:guid}/Actions")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ExternalActionPerformed([FromRoute]Guid documentId, [FromBody] DocumentActionNotificationRequest action)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_allowedExternalActions.Contains(action.Action))
|
||||
{
|
||||
throw new BadHttpRequestException($"{action.Action.ToString()} action type not allowed externally.");
|
||||
}
|
||||
|
||||
await _documentAuditService.AuditAction(documentId,
|
||||
HttpContext.User.Claims.Single(c => c.Type == "name").Value, action.Source, action.Action);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status400BadRequest, ex.Message);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UploadDocument([FromBody] DocumentRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UploadDocument(request);
|
||||
return CreatedAtAction(nameof(GetDocumentById), new { documentId = result.DocumentId }, result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocument([FromBody] UpdateDocumentRequest request, Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UpdateDocument(request, documentId, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("DocumentContent/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocumentContent([FromBody] UpdateDocumentContentRequest request, Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UpdateDocumentContent(request, documentId, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("DocumentInformation/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocumentInformation([FromBody] UpdateDocumentInformationRequest request,
|
||||
Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UpdateDocumentInformation(request, documentId, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("DocumentInformation/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentInformation(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentInformation(documentId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("DocumentScanStatus")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocumentScanStatus([FromBody] UpdateDocumentScanStatusRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _documentService.UpdateDocumentScanStatus(request.ScanStatus, request.DocumentBlobIds, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
return Ok();
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is currently a developer endpoint, still waiting on guidance on how the 'Delete' Operation
|
||||
/// should work. This will remove both the documents contents from blob storage and its information from cosmos.
|
||||
/// </summary>
|
||||
/// <param name="documentId"></param>
|
||||
/// <returns></returns>
|
||||
[ApiKey]
|
||||
[HttpDelete("{documentId:guid}")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> DeleteDocument(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _documentService.DeleteDocument(documentId);
|
||||
return Ok();
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpDelete("DeleteDocumentContent")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> DeleteDocumentContent([FromBody] DeleteDocumentContentRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _documentService.DeleteDocumentContent(request);
|
||||
return Ok();
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost("Count")]
|
||||
[ProducesResponseType(typeof(long), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> CountDocuments(DocumentInformationFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var count = await _documentService.CountDocuments(filter);
|
||||
|
||||
return Ok(count);
|
||||
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Unexpected Exception");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost("GetDocumentsForCleanup")]
|
||||
[ProducesResponseType(typeof(IEnumerable<Guid>), 200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentsForCleanup([FromBody] GetDocumentCleanupRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documents = await _documentService.GetDocumentsForCleanup(request);
|
||||
|
||||
if (!documents.Any())
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Ok(documents);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Unexpected Exception");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Net;
|
||||
using LCU.API.Internal.Document.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
using LCU.Library.Internal.ApiSecurity.Attributes;
|
||||
|
||||
namespace LCU.API.Internal.Document.Controllers;
|
||||
|
||||
[Route("[controller]")]
|
||||
[ApiController]
|
||||
public class StatusController : ControllerBase
|
||||
{
|
||||
private readonly IEnvironmentView _environmentView;
|
||||
|
||||
public StatusController(IEnvironmentView environmentView)
|
||||
{
|
||||
_environmentView = environmentView;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Verbose($"{_environmentView.Application}'s Status Endpoint Was Hit");
|
||||
return Ok(_environmentView);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure to return status");
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Exceptions;
|
||||
|
||||
public class BlobApiServiceException : Exception
|
||||
{
|
||||
public BlobApiServiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public BlobApiServiceException(string message, Exception exception) : base(message, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Exceptions;
|
||||
|
||||
public class DocumentAuditServiceException : Exception
|
||||
{
|
||||
public DocumentAuditServiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DocumentAuditServiceException(string message, Exception exception) : base(message, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Exceptions;
|
||||
|
||||
public class DocumentInformationServiceException : Exception
|
||||
{
|
||||
public DocumentInformationServiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DocumentInformationServiceException(string message, Exception exception) : base(message, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>1701;1702; 8618; 1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<NoWarn>1701;1702; 8618; 1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.13.2" />
|
||||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.3.0" />
|
||||
<PackageReference Include="LCU.Api.Internal.MaidBotLib" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.ApiSecurity" Version="1.0.1" />
|
||||
<PackageReference Include="LCU.Library.Internal.Exceptions" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Logging" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Identity.Web" Version="1.25.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Configuration" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LCU.API.Internal.DocumentLib\LCU.API.Internal.DocumentLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties appsettings_1staging_1json__JsonSchema="https://github.com/fbecart/zinoma/releases/latest/download/zinoma-schema.json" /></VisualStudio></ProjectExtensions>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="LCU.Library.Internal.DocumentModelsTest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace LCU.API.Internal.Document.Models;
|
||||
|
||||
public class EnvironmentView : IEnvironmentView
|
||||
{
|
||||
private readonly IWebHostEnvironment _env;
|
||||
|
||||
public EnvironmentView(IWebHostEnvironment env)
|
||||
{
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public string Application => _env.ApplicationName;
|
||||
public DateTime CurrentDate => DateTime.Now;
|
||||
public string BuildVersion => AssembleBuildVersion();
|
||||
public DateTime BuildDate => AssemblyBuildDate();
|
||||
public string Environment => _env.EnvironmentName;
|
||||
|
||||
private static DateTime AssemblyBuildDate()
|
||||
{
|
||||
//https://stackoverflow.com/a/804310/266594
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var fileInfo = new FileInfo(assembly.Location);
|
||||
var lastModified = fileInfo.LastWriteTime;
|
||||
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
private static string AssembleBuildVersion()
|
||||
{
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
return version == null ? "-.-.-" : version.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace LCU.API.Internal.Document.Models;
|
||||
|
||||
public interface IEnvironmentView
|
||||
{
|
||||
string Application { get; }
|
||||
DateTime CurrentDate { get; }
|
||||
string BuildVersion { get; }
|
||||
DateTime BuildDate { get; }
|
||||
string Environment { get; }
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Azure.Identity;
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.API.Internal.Document.Models;
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.Logging;
|
||||
using LCU.Library.Internal.Logging.Middleware;
|
||||
using LCU.Library.Internal.Logging.Models;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using LCU.Library.Internal.ApiSecurity;
|
||||
using Serilog;
|
||||
using LCU.API.Internal.DocumentLib.Utilities;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Identity.Web;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using LCU.Library.Internal.Configuration;
|
||||
using Microsoft.FeatureManagement;
|
||||
|
||||
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||||
|
||||
var builder = BuildBuilder(args);
|
||||
BuildServicesAndConfigurations(builder);
|
||||
BuildAppAndRun(builder);
|
||||
|
||||
WebApplicationBuilder BuildBuilder(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration.AddJsonFile("/app/config/mapped-appsettings/primary.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/documentapi_configuration.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-azure-client-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-azure-tenant-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-client-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-client-secret.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-tenant-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-swagger-oauth-scope.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-seq-api-key.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-blob-storage-connection-string.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-mongo-database-connection-string.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-type-uploader-api-key.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-type-retriever-api-key.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-type-cleanup-api-key.json", optional: true, reloadOnChange: true);
|
||||
|
||||
if (!bool.Parse(builder.Configuration.GetSection("Primary")["IsAksDeploy"]))
|
||||
{
|
||||
var clientSecretCredentials = new ClientSecretCredential(builder.Configuration["TenantId"], builder.Configuration["ClientId"], builder.Configuration["ClientSecret"]);
|
||||
|
||||
builder.Configuration.AddAzureKeyVault(
|
||||
new Uri($"https://{builder.Configuration["AzureKeyVaultUrl"]}.vault.azure.net/"), clientSecretCredentials);
|
||||
}
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
return builder;
|
||||
}
|
||||
|
||||
void BuildServicesAndConfigurations(WebApplicationBuilder builder)
|
||||
{
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddMicrosoftIdentityWebApi(configuration.GetSection("AzureAd"));
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("user_token", policy =>
|
||||
{
|
||||
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
|
||||
policy.RequireAuthenticatedUser();
|
||||
});
|
||||
options.DefaultPolicy = options.GetPolicy("user_token")!;
|
||||
});
|
||||
|
||||
services.AddFeatureManagement();
|
||||
|
||||
var documentTypeConfiguration = configuration.Bind<ApiConfiguration>(services, "Primary");
|
||||
configuration.Bind<BlobStorageConfiguration>(services, "BlobStorage");
|
||||
configuration.Bind<DatabaseConfiguration>(services, "MongoDatabase");
|
||||
configuration.Bind<NotificationApiConfiguration>(services);
|
||||
|
||||
var loggerConfiguration = new LoggingConfiguration(documentTypeConfiguration.AppName,
|
||||
documentTypeConfiguration.SeqApiKey,
|
||||
documentTypeConfiguration.SeqUrl);
|
||||
|
||||
var lcuLogging = new LcuLogging(loggerConfiguration);
|
||||
lcuLogging.UseLcuLogging();
|
||||
|
||||
ServiceRegistrations.AddDocumentServices(services);
|
||||
ServiceRegistrations.AddDocumentDataAccesses(services);
|
||||
ServiceRegistrations.AddDocumentRepositories(services);
|
||||
|
||||
BsonSerializer.RegisterDiscriminatorConvention(typeof(DocumentAudit), new DocumentAuditDiscriminatorConvention());
|
||||
|
||||
services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen(s =>
|
||||
{
|
||||
s.SwaggerDoc("v1", new OpenApiInfo { Title = $"{documentTypeConfiguration.AppName}", Version = "v1" });
|
||||
s.UseOneOfForPolymorphism();
|
||||
|
||||
var authority = $"https://login.microsoftonline.com/{configuration.GetSection("AzureAd")["TenantId"]}";
|
||||
s.AddSecurityDefinition
|
||||
(
|
||||
nameof(SecuritySchemeType.OpenIdConnect),
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Flows = new OpenApiOAuthFlows
|
||||
{
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri($"{authority}/oauth2/v2.0/authorize"),
|
||||
TokenUrl = new Uri($"{authority}/oauth2/v2.0/token")
|
||||
}
|
||||
}
|
||||
//24.10.2022 these SHOULD have enabled OIDC discovery (the discovery info is returned from this URI) but swagger UI is not displaying the auth options
|
||||
//oidc discovery does work, I have set it up with IdentityServer4 in the past
|
||||
//Type = SecuritySchemeType.OpenIdConnect,
|
||||
//OpenIdConnectUrl = new Uri($"https://login.microsoftonline.com/{configuration.GetSection("AzureAd")["TenantId"]}/v2.0/.well-known/openid-configuration")
|
||||
}
|
||||
);
|
||||
s.AddSecurityRequirement
|
||||
(
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = nameof(SecuritySchemeType.OpenIdConnect)
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
s.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = "ApiKey"
|
||||
});
|
||||
s.AddSecurityDefinition("ApiClient", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = "Client"
|
||||
});
|
||||
s.AddSecurityRequirement
|
||||
(
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "ApiKey"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
}
|
||||
);
|
||||
s.AddSecurityRequirement
|
||||
(
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "ApiClient"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
services.AddApiKeySecurity(configuration);
|
||||
}
|
||||
|
||||
void BuildAppAndRun(WebApplicationBuilder builder)
|
||||
{
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment()) app.UseDeveloperExceptionPage();
|
||||
|
||||
app.UseSwagger(s =>
|
||||
{
|
||||
s.PreSerializeFilters.Add((doc, request) =>
|
||||
{
|
||||
doc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{request.Scheme}://{request.Host.Value}{request.PathBase}" } };
|
||||
});
|
||||
});
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("v1/swagger.json", "LCU.API.Internal.Document v1");
|
||||
|
||||
c.OAuthClientId(app.Configuration.GetSection("AzureAd")["ClientId"]);
|
||||
c.OAuthUsePkce();
|
||||
c.OAuthScopes(app.Configuration.GetSection("Swagger")["OAuthScope"]);
|
||||
});
|
||||
app.UseMiddleware<RequestResponseLoggingMiddleware>();
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest;
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
app.UseCors(MyAllowSpecificOrigins);
|
||||
app.UseHttpsRedirection();
|
||||
app.UseAuthentication().UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
|
||||
|
||||
app.Run();
|
||||
}
|
||||
|
||||
public static class ServiceRegistrations
|
||||
{
|
||||
public static void AddDocumentServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<DocumentService.DocumentIsForMemberStrategy>(p =>
|
||||
{
|
||||
const string checkDocumentOwner = "GetDocumentCheckOwnership";
|
||||
|
||||
return p.GetRequiredService<IFeatureManager>().IsEnabledAsync(checkDocumentOwner).Result
|
||||
? DocumentService.Ownership.CheckPersonOrOrgNumber.Strategy
|
||||
: DocumentService.Ownership.Unchecked.Strategy;
|
||||
});
|
||||
services.AddScoped<IDocumentService, DocumentService>();
|
||||
services.AddScoped<IDocumentInformationService, DocumentInformationService>();
|
||||
services.AddScoped<IBlobApiService, BlobApiService>();
|
||||
services.AddScoped<IDocumentAuditService, DocumentAuditService>();
|
||||
services.AddScoped<IEnvironmentView, EnvironmentView>();
|
||||
services.AddScoped<INotificationApiService, NotificationApiService>();
|
||||
}
|
||||
|
||||
public static void AddDocumentDataAccesses(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IBlobStorageClient, BlobStorageClient>();
|
||||
services.AddScoped<IBlobApiDao, BlobApiDao>();
|
||||
}
|
||||
|
||||
public static void AddDocumentRepositories(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IDataClient, DataClient>();
|
||||
services.AddTransient<IMongoCollectionFactory, MongoCollectionFactory>();
|
||||
services.AddScoped<IDocumentInformationRepository, DocumentInformationRepository>();
|
||||
services.AddScoped<IDocumentAuditRepository, DocumentAuditRepository>();
|
||||
services.AddScoped<INotificationApiDao, NotificationApiDao>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:9009",
|
||||
"sslPort": 44312
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"LCU.API.Internal.Document": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7037;http://localhost:5037",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public class BlobApiService : IBlobApiService
|
||||
{
|
||||
private readonly IBlobApiDao _blobApiDao;
|
||||
|
||||
public BlobApiService(IBlobApiDao blobApiDao)
|
||||
{
|
||||
_blobApiDao = blobApiDao;
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetDocumentContent(Guid? documentBlobId)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var result = await _blobApiDao.GetDocumentFromBlob(documentBlobId ?? Guid.Empty);
|
||||
|
||||
if (result?.Value == null){
|
||||
throw new ServiceException($"Could not find the Document Content for DocumentBlobId:{documentBlobId}");
|
||||
}
|
||||
|
||||
await using var memoryStream = new MemoryStream();
|
||||
await result.Value.Content.CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
catch (ServiceException exception){
|
||||
Log.Error(exception.Message);
|
||||
throw new BlobApiServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document Content with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Guid> UploadDocumentContent(byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _blobApiDao.SaveDocumentToBlob(content);
|
||||
}
|
||||
catch (Exception exception){
|
||||
const string message = "Failure to Upload Document Content.";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateDocumentContent(Guid? documentBlobId, byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
throw new Exception("DocumentBlobId cannot be null.");
|
||||
}
|
||||
|
||||
await _blobApiDao.UpdateDocumentInBlob(documentBlobId ?? Guid.Empty, content);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document with DocumentBlobId: {documentBlobId}";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocumentContent(Guid? documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
throw new Exception("DocumentBlobId cannot be null.");
|
||||
}
|
||||
|
||||
await _blobApiDao.RemoveDocumentFromBlob(documentBlobId ?? Guid.Empty);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Delete Document with DocumentBlobId: {documentBlobId}";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public class DocumentAuditService : IDocumentAuditService
|
||||
{
|
||||
private const string NameField = "Name";
|
||||
private const string TypeField = "Type";
|
||||
private const string AttributesField = "Attributes";
|
||||
private const string ScanStatusField = "ScanStatus";
|
||||
private const string ContentField = "Content";
|
||||
private readonly IDocumentAuditRepository _documentAuditRepository;
|
||||
|
||||
public DocumentAuditService(IDocumentAuditRepository documentAuditRepository)
|
||||
{
|
||||
_documentAuditRepository = documentAuditRepository;
|
||||
}
|
||||
|
||||
public async Task<List<DocumentAuditResponse>> GetDocumentAudits(params Guid[] documentIds)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
static DocumentAuditResponse ResponseFactory(DocumentAudit audit) => audit switch
|
||||
{
|
||||
DocumentActionAudit actionAudit => new DocumentActionAuditResponse
|
||||
{
|
||||
Action = actionAudit.Action,
|
||||
ActionDescription = actionAudit.ActionDescription,
|
||||
Date = actionAudit.Date,
|
||||
DocumentId = actionAudit.DocumentId,
|
||||
Source = actionAudit.Source,
|
||||
UserId = actionAudit.UserId
|
||||
},
|
||||
DocumentFieldAudit fieldAudit => new DocumentFieldAuditResponse
|
||||
{
|
||||
Field = fieldAudit.Field,
|
||||
CurrentValue = fieldAudit.CurrentValue,
|
||||
PreviousValue = fieldAudit.PreviousValue,
|
||||
Date = fieldAudit.Date,
|
||||
DocumentId = fieldAudit.DocumentId,
|
||||
Source = fieldAudit.Source,
|
||||
UserId = fieldAudit.UserId
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(audit))
|
||||
};
|
||||
|
||||
return (await _documentAuditRepository.GetDocumentAudits(documentIds))
|
||||
.Select(ResponseFactory)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve Document Audit Information for DocumentIds: {documentIds}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentUploadedAudit(Guid documentId, DateTime uploadDate)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentId,
|
||||
"System",
|
||||
uploadDate,
|
||||
"System",
|
||||
DocumentAction.Upload,
|
||||
"Document Uploaded"
|
||||
)
|
||||
};
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(audits);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Create Document Audit for DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentDeletedAudit(Guid documentId,
|
||||
DateTime deleteDate)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentId,
|
||||
"System",
|
||||
deleteDate,
|
||||
"System",
|
||||
DocumentAction.Delete,
|
||||
"Document Deleted"
|
||||
)
|
||||
};
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(audits);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Create Document Audit for DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentContentDeletedAudit(Guid documentId,
|
||||
DateTime deleteDate,
|
||||
string? reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentId,
|
||||
"System",
|
||||
deleteDate,
|
||||
"System",
|
||||
DocumentAction.DeleteContent,
|
||||
"DocumentContent Deleted" + (!string.IsNullOrWhiteSpace(reason) ? $". Reason: {reason}" : string.Empty)
|
||||
)
|
||||
};
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(audits);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Create Document Audit for DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentAudits(DocumentInformation oldDocumentInformation,
|
||||
DocumentInformation newDocumentInformation, bool wasDocumentContentUpdated, DateTime updateDate, string userId)
|
||||
{
|
||||
DocumentFieldAudit AuditLogFactory(string field, object? oldValue, object? newValue) => new
|
||||
(
|
||||
newDocumentInformation.DocumentId,
|
||||
newDocumentInformation.Source,
|
||||
updateDate,
|
||||
userId,
|
||||
field,
|
||||
oldValue,
|
||||
newValue
|
||||
);
|
||||
|
||||
var documentAudits = new List<DocumentFieldAudit>();
|
||||
|
||||
try
|
||||
{
|
||||
if (oldDocumentInformation.Name != newDocumentInformation.Name){
|
||||
documentAudits.Add(AuditLogFactory(NameField, oldDocumentInformation.Name, newDocumentInformation.Name));
|
||||
}
|
||||
|
||||
if (oldDocumentInformation.Type != newDocumentInformation.Type){
|
||||
documentAudits.Add(AuditLogFactory(TypeField, oldDocumentInformation.Type, newDocumentInformation.Type));
|
||||
}
|
||||
|
||||
if (oldDocumentInformation.ScanStatus != newDocumentInformation.ScanStatus){
|
||||
documentAudits.Add(AuditLogFactory(ScanStatusField, oldDocumentInformation.ScanStatus, newDocumentInformation.ScanStatus));
|
||||
}
|
||||
|
||||
if (!AttributesAreEqual(oldDocumentInformation.Attributes, newDocumentInformation.Attributes)){
|
||||
documentAudits.Add(AuditLogFactory(AttributesField, oldDocumentInformation.Attributes, newDocumentInformation.Attributes));
|
||||
}
|
||||
|
||||
if (wasDocumentContentUpdated){
|
||||
documentAudits.Add(AuditLogFactory(ContentField, "NOT CAPTURED", "NOT CAPTURED"));
|
||||
}
|
||||
|
||||
if (!documentAudits.Any()){
|
||||
return new List<DocumentAudit>();
|
||||
}
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(documentAudits.ToArray());
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message =
|
||||
$"Failure to Create Document Audit Information for DocumentId: {oldDocumentInformation.DocumentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AuditAction(Guid documentId, string userId, string actionSource, DocumentAction action)
|
||||
{
|
||||
try
|
||||
{
|
||||
var description = action switch
|
||||
{
|
||||
DocumentAction.Download => $"Document downloaded from {actionSource}.",
|
||||
DocumentAction.Upload => "Document Uploaded",
|
||||
DocumentAction.Delete => "Document Deleted",
|
||||
DocumentAction.DeleteContent => "DocumentContent Deleted",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
await _documentAuditRepository.SaveDocumentAudits(new DocumentActionAudit(documentId, actionSource, DateTime.Now, userId, action, description));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message =
|
||||
$"Failure to Create Document Audit Information for DocumentId: {documentId} on DocumentAction: {action} from source {actionSource}";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new DocumentAuditServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AttributesAreEqual(Dictionary<string, string>? oldAttributes, Dictionary<string, string>? newAttributes)
|
||||
{
|
||||
oldAttributes ??= new Dictionary<string, string>();
|
||||
newAttributes ??= new Dictionary<string, string>();
|
||||
|
||||
if (oldAttributes.Count != newAttributes.Count){
|
||||
return false;
|
||||
}
|
||||
|
||||
var attributes = oldAttributes
|
||||
.Where(d2 => newAttributes.Any(d1 => d1.Key == d2.Key && d2.Value.Equals(d1.Value))).ToList();
|
||||
return attributes.Count == oldAttributes.Count;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public class DocumentInformationService : IDocumentInformationService
|
||||
{
|
||||
private readonly IDocumentInformationRepository _documentInformationRepository;
|
||||
private readonly ApiConfiguration _apiConfiguration;
|
||||
|
||||
public DocumentInformationService(IDocumentInformationRepository documentInformationRepository,
|
||||
ApiConfiguration apiConfiguration)
|
||||
{
|
||||
_documentInformationRepository = documentInformationRepository;
|
||||
_apiConfiguration = apiConfiguration;
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation?> GetDocumentInformation(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null ||
|
||||
documentInformation.UploadDate >= DateTime.UtcNow.AddHours(_apiConfiguration.FileUploadCutoffHours) ||
|
||||
documentInformation.ScanStatus == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return documentInformation;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve DocumentInformation with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation?> GetDocumentInformationByBlobId(Guid? documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformationByBlobId(documentBlobId ?? Guid.Empty);
|
||||
return documentInformation ?? null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve DocumentInformation with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocumentInformation(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _documentInformationRepository.RemoveDocumentInformation(documentId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Delete DocumentInformation with DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation> UploadDocumentInformation(DocumentInformation information)
|
||||
{
|
||||
try{
|
||||
return await _documentInformationRepository.SaveDocumentInformation(information);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Upload DocumentInformation with Name: {information.Name} of type: {information.Type} from source: {information.Source}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation?> UpdateDocumentInformation(Guid documentId, DocumentInformation information)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await _documentInformationRepository.UpdateDocumentInformation(documentId, information);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Update DocumentInformation with DocumentId: {documentId} and Name: {information.Name} of type: {information.Type}.";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<DocumentInformation>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformationFound = await _documentInformationRepository.SearchDocumentInformation(filter, skip, take, sortDefinition);
|
||||
return documentInformationFound ?? new List<DocumentInformation>();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to Search Document Information";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> CountDocuments(DocumentInformationFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationRepository.CountDocuments(filter);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to Count Document Information";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationRepository.GetDocumentsForCleanup(request);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to GetDocumentsForCleanup";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,748 @@
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using LCU.Api.Internal.MaidBotLib.Models;
|
||||
using Serilog;
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
// TODO: Add hash of content to document information so the content can be compared before updates.
|
||||
// Any basic hash should be fine since it will only be used as a checksum to the content.
|
||||
// Example can be found https://stackoverflow.com/questions/10520048/calculate-md5-checksum-for-a-file/10520086#10520086
|
||||
public class DocumentService : IDocumentService
|
||||
{
|
||||
private readonly IBlobApiService _blobApiService;
|
||||
private readonly IDocumentInformationService _documentInformationService;
|
||||
private readonly IDocumentAuditService _documentAuditService;
|
||||
private readonly INotificationApiService _notificationApiService;
|
||||
private readonly ApiConfiguration _apiConfiguration;
|
||||
private readonly DocumentIsForMemberStrategy DocumentIsForMember;
|
||||
|
||||
public DocumentService
|
||||
(
|
||||
IBlobApiService blobApiService,
|
||||
IDocumentInformationService documentInformationService,
|
||||
IDocumentAuditService documentAuditService,
|
||||
INotificationApiService notificationApiService,
|
||||
ApiConfiguration apiConfiguration,
|
||||
DocumentIsForMemberStrategy documentIsForMember
|
||||
)
|
||||
{
|
||||
_blobApiService = blobApiService;
|
||||
_documentInformationService = documentInformationService;
|
||||
_documentAuditService = documentAuditService;
|
||||
_notificationApiService = notificationApiService;
|
||||
_apiConfiguration = apiConfiguration;
|
||||
DocumentIsForMember = documentIsForMember;
|
||||
}
|
||||
|
||||
public async Task<DocumentResponse?> GetDocument(Guid documentId)
|
||||
{
|
||||
try{
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DocumentResponse
|
||||
{
|
||||
Content = await _blobApiService.GetDocumentContent(documentInformation.DocumentBlobId),
|
||||
DocumentInformation = ConvertDocumentInformationToDocumentInformationResponse(documentInformation)
|
||||
};
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Retrieve Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentResponse?> GetDocument(long personOrOrgNumber, Guid documentId)
|
||||
{
|
||||
try{
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!DocumentIsForMember(documentInformation, personOrOrgNumber)) return null;
|
||||
|
||||
return new DocumentResponse
|
||||
{
|
||||
Content = await _blobApiService.GetDocumentContent(documentInformation.DocumentBlobId),
|
||||
DocumentInformation = ConvertDocumentInformationToDocumentInformationResponse(documentInformation)
|
||||
};
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Retrieve Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]?> GetDocumentByBlobId(Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _blobApiService.GetDocumentContent(documentBlobId);
|
||||
}
|
||||
catch (BlobApiServiceException exception)
|
||||
{
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve Document with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]?> GetDocumentByBlobId(long personOrOrgNumber, Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var document = await _documentInformationService.GetDocumentInformationByBlobId(documentBlobId);
|
||||
if(document is null) return null;
|
||||
|
||||
if(!DocumentIsForMember(document, personOrOrgNumber)) return null;
|
||||
|
||||
return await _blobApiService.GetDocumentContent(documentBlobId);
|
||||
}
|
||||
catch (BlobApiServiceException exception)
|
||||
{
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve Document with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocument(Guid documentId)
|
||||
{
|
||||
try{
|
||||
var deleteDate = DateTime.UtcNow;
|
||||
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _documentInformationService.DeleteDocumentInformation(documentId);
|
||||
await _blobApiService.DeleteDocumentContent(documentInformation.DocumentBlobId);
|
||||
await _documentAuditService.CreateDocumentDeletedAudit(documentInformation.DocumentId, deleteDate);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Remove Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Remove Document Content from Blob Storage, but DocumentInformation was removed. Exception Message: {exception.Message}";
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)).Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Delete Document with DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocumentContent(DeleteDocumentContentRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deleteDate = DateTime.UtcNow;
|
||||
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(request.DocumentId);
|
||||
if (documentInformation?.DocumentBlobId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _blobApiService.DeleteDocumentContent(documentInformation.DocumentBlobId);
|
||||
|
||||
documentInformation.DocumentBlobId = null;
|
||||
await _documentInformationService.UpdateDocumentInformation(documentInformation.DocumentId, documentInformation);
|
||||
|
||||
await _documentAuditService.CreateDocumentContentDeletedAudit(documentInformation.DocumentId, deleteDate, request.Reason);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message =
|
||||
$"Failure to Remove Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Remove Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)).Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Delete Document Content with DocumentId: {request.DocumentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse> UploadDocument(DocumentRequest request)
|
||||
{
|
||||
var docBlobId = Guid.Empty;
|
||||
try{
|
||||
docBlobId = await _blobApiService.UploadDocumentContent(request.Content);
|
||||
var fileSize = CalculateFileSize(request.Content);
|
||||
var uploadDate = DateTime.UtcNow;
|
||||
|
||||
var documentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = Guid.NewGuid(),
|
||||
DocumentBlobId = docBlobId,
|
||||
TraceId = request.TraceId,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
Source = request.Source,
|
||||
DataSource = request.DataSource,
|
||||
PersonNumbers = request.PersonNumbers,
|
||||
OrganizationNumbers = request.OrganizationNumbers,
|
||||
AccountNumber = request.AccountNumber,
|
||||
Attributes = request.Attributes,
|
||||
CreatedDate = request.CreatedDate,
|
||||
FileSize = fileSize,
|
||||
UploadDate = DateTime.UtcNow,
|
||||
ScanStatus = null
|
||||
};
|
||||
|
||||
var uploadedDocumentInformation = await _documentInformationService.UploadDocumentInformation(documentInformation);
|
||||
await _documentAuditService.CreateDocumentUploadedAudit(documentInformation.DocumentId, uploadDate);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(uploadedDocumentInformation);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message = $"Failure to Upload Content to Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
await RollBackDocumentContent(docBlobId);
|
||||
|
||||
var message = $"Failure to Upload Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message =
|
||||
$"Failure to Upload Document Name: {request.Name} of type: {request.Type} from source: {request.Source}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> UpdateDocument(UpdateDocumentRequest request, Guid documentId, string userId)
|
||||
{
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var content = request.Content;
|
||||
var fileSize = CalculateFileSize(content);
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = documentId,
|
||||
DocumentBlobId = documentInformation.DocumentBlobId,
|
||||
TraceId = documentInformation.TraceId,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
PersonNumbers = request.PersonNumbers,
|
||||
OrganizationNumbers = request.OrganizationNumbers,
|
||||
AccountNumber = request.AccountNumber,
|
||||
Attributes = request.Attributes,
|
||||
LastModifiedDate = modifiedDate,
|
||||
FileSize = fileSize,
|
||||
ScanStatus = null
|
||||
};
|
||||
|
||||
var docInfo = await _documentInformationService.UpdateDocumentInformation(documentId, updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, true, modifiedDate, userId);
|
||||
await _blobApiService.UpdateDocumentContent(documentInformation.DocumentBlobId, content);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(docInfo);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId}. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Update Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document with DocumentId: {documentId} Name: {request.Name} of type: {request.Type} from source: {request.UpdateSource}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> GetDocumentInformation(Guid documentId)
|
||||
{
|
||||
try{
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(documentInformation);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Retrieve Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> UpdateDocumentContent(UpdateDocumentContentRequest request, Guid documentId, string userId)
|
||||
{
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var content = request.Content;
|
||||
var fileSize = CalculateFileSize(content);
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = documentId,
|
||||
DocumentBlobId = documentInformation.DocumentBlobId,
|
||||
TraceId = documentInformation.TraceId,
|
||||
Name = documentInformation.Name,
|
||||
Type = documentInformation.Type,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
PersonNumbers = documentInformation.PersonNumbers,
|
||||
OrganizationNumbers = documentInformation.OrganizationNumbers,
|
||||
AccountNumber = documentInformation.AccountNumber,
|
||||
Attributes = documentInformation.Attributes,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
LastModifiedDate = modifiedDate,
|
||||
FileSize = fileSize,
|
||||
ScanStatus = null
|
||||
};
|
||||
|
||||
var docInfo = await _documentInformationService.UpdateDocumentInformation(documentId, updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, true, modifiedDate, userId);
|
||||
await _blobApiService.UpdateDocumentContent(documentInformation.DocumentBlobId, content);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(docInfo);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId}. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Update Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document Content for DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> UpdateDocumentInformation(UpdateDocumentInformationRequest request, Guid documentId, string userId)
|
||||
{
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = documentId,
|
||||
DocumentBlobId = documentInformation.DocumentBlobId,
|
||||
TraceId = documentInformation.TraceId,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
PersonNumbers = request.PersonNumbers,
|
||||
OrganizationNumbers = request.OrganizationNumbers,
|
||||
AccountNumber = request.AccountNumber,
|
||||
Attributes = request.Attributes,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
LastModifiedDate = modifiedDate,
|
||||
FileSize = documentInformation.FileSize,
|
||||
ScanStatus = documentInformation.ScanStatus
|
||||
};
|
||||
|
||||
var docInfo = await _documentInformationService.UpdateDocumentInformation(documentId, updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, false, modifiedDate, userId);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(docInfo);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId} with Name: {request.Name} of type: {request.Type} from source: {request.UpdateSource}. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId} with Name: {request.Name} of type: {request.Type} from source: {request.UpdateSource}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<DocumentInformationResponse>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null)
|
||||
{
|
||||
try{
|
||||
var documentInformationFound = await _documentInformationService.SearchDocumentInformation(filter, skip, take, sortDefinition);
|
||||
|
||||
return documentInformationFound.Select(ConvertDocumentInformationToDocumentInformationResponse).ToList();
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
Log.Error(exception, exception.Message);
|
||||
throw new ServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
const string message = "Failure to Search Document Information";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateDocumentScanStatus(bool status, List<Guid> documentBlobIds, string userId)
|
||||
{
|
||||
foreach (var documentBlobId in documentBlobIds){
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformationByBlobId(documentBlobId);
|
||||
if (documentInformation == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentBlobId = documentBlobId,
|
||||
|
||||
DocumentId = documentInformation.DocumentId,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
TraceId = documentInformation.TraceId,
|
||||
Name = documentInformation.Name,
|
||||
Type = documentInformation.Type,
|
||||
PersonNumbers = documentInformation.PersonNumbers,
|
||||
OrganizationNumbers = documentInformation.OrganizationNumbers,
|
||||
AccountNumber = documentInformation.AccountNumber,
|
||||
Attributes = documentInformation.Attributes,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
FileSize = documentInformation.FileSize,
|
||||
|
||||
ScanStatus = status,
|
||||
LastModifiedDate = modifiedDate
|
||||
};
|
||||
|
||||
await _documentInformationService.UpdateDocumentInformation(updatedDocumentInformation.DocumentId,
|
||||
updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, false,
|
||||
modifiedDate, userId);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
await NotifyAboutMaliciousFile(documentInformation);
|
||||
}
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
Log.Error(exception, $"Failure to Update Document Scan Status for DocumentBlobId: {documentBlobId}");
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentInformation.DocumentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
Log.Error(exception,$"Failure to Update Document Scan Status for DocumentBlobId: {documentBlobId}, caused by unexpected exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> CountDocuments(DocumentInformationFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationService.CountDocuments(filter);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception)
|
||||
{
|
||||
Log.Error(exception, exception.Message);
|
||||
|
||||
throw new ServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to Count Document Information";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationService.GetDocumentsForCleanup(request);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception)
|
||||
{
|
||||
Log.Error(exception, exception.Message);
|
||||
|
||||
throw new ServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure GetDocumentsForCleanup";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RollBackDocumentContent(Guid docBlobId)
|
||||
{
|
||||
try{
|
||||
Log.Information($"Removing {docBlobId} from Blob Storage, as part of a rollback transaction.");
|
||||
await _blobApiService.DeleteDocumentContent(docBlobId);
|
||||
}
|
||||
catch (Exception inception){
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
|
||||
.Error(inception,
|
||||
$"Failed to remove {docBlobId} from Blob Storage, an inconsistency in data now exists between DocumentInformation and Content. Exception Message: {inception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RollbackDocumentInformation(Guid documentId, DocumentInformation documentInformation)
|
||||
{
|
||||
try{
|
||||
Log.Information(
|
||||
$"Reverting Document Information for DocumentId:{documentId}, as part of a rollback transaction.");
|
||||
await _documentInformationService.UpdateDocumentInformation(documentId, documentInformation);
|
||||
}
|
||||
catch (Exception inception){
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
|
||||
.Error($"Failed to update document information for DocumentId:{documentId}, an inconsistency in data now exists between DocumentInformation. DocumentInformation should be updated to: {JsonSerializer.Serialize(documentInformation)} Exception Message: {inception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static BigInteger CalculateFileSize(byte[] bytes)
|
||||
{
|
||||
var fileBase64Value = Convert.ToBase64String(bytes);
|
||||
var size = fileBase64Value.Length / 4 * 3;
|
||||
|
||||
if (fileBase64Value.EndsWith("=="))
|
||||
size -= 2;
|
||||
else if (fileBase64Value.EndsWith("=")) size -= 1;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private static DocumentInformationResponse ConvertDocumentInformationToDocumentInformationResponse(
|
||||
DocumentInformation updatedDocumentInformation)
|
||||
{
|
||||
return new DocumentInformationResponse
|
||||
{
|
||||
DocumentId = updatedDocumentInformation.DocumentId,
|
||||
DocumentBlobId = updatedDocumentInformation.DocumentBlobId,
|
||||
TraceId = updatedDocumentInformation.TraceId,
|
||||
Name = updatedDocumentInformation.Name,
|
||||
Type = updatedDocumentInformation.Type,
|
||||
Source = updatedDocumentInformation.Source,
|
||||
DataSource = updatedDocumentInformation.DataSource,
|
||||
FileSize = updatedDocumentInformation.FileSize,
|
||||
ScanStatus = updatedDocumentInformation.ScanStatus,
|
||||
PersonNumbers = updatedDocumentInformation.PersonNumbers,
|
||||
OrganizationNumbers = updatedDocumentInformation.OrganizationNumbers,
|
||||
AccountNumber = updatedDocumentInformation.AccountNumber,
|
||||
Attributes = updatedDocumentInformation.Attributes,
|
||||
UploadDate = updatedDocumentInformation.UploadDate,
|
||||
LastModifiedDate = updatedDocumentInformation.LastModifiedDate,
|
||||
CreatedDate = updatedDocumentInformation.CreatedDate
|
||||
};
|
||||
}
|
||||
|
||||
private async Task NotifyAboutMaliciousFile(DocumentInformation documentInformation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var documentAttributesSerialized =
|
||||
JsonSerializer.Serialize(
|
||||
documentInformation.Attributes,
|
||||
new JsonSerializerOptions { WriteIndented = true }
|
||||
);
|
||||
|
||||
var emailRequest = new EmailRequest()
|
||||
{
|
||||
Subject = "DocumentApi: Malicious File Reported",
|
||||
Recipient = _apiConfiguration.MaliciousFileFoundEmailList,
|
||||
Body = "This is an auto generated message. <br>" +
|
||||
"<br>" +
|
||||
"Azure Defender has returned a malicious file from scan<br>" +
|
||||
"<br>" +
|
||||
"Document Attributes:<br>" +
|
||||
$"{documentAttributesSerialized}"
|
||||
};
|
||||
|
||||
await _notificationApiService.SendEmailAsync(emailRequest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = $"Failure to send email. Azure Defender returned a file as malicious. DocumentInformation: {JsonSerializer.Serialize(documentInformation)}. Exception Message: {ex.Message}";
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)).Error(ex, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate bool DocumentIsForMemberStrategy(DocumentInformation document, long personOrOrgNumber);
|
||||
|
||||
public static class Ownership
|
||||
{
|
||||
public static class Unchecked
|
||||
{
|
||||
public static DocumentIsForMemberStrategy Strategy => DocumentIsForMember;
|
||||
|
||||
private static bool DocumentIsForMember(DocumentInformation document, long personOrOrgNumber)
|
||||
{
|
||||
var personOrOrgNumbers = (document.PersonNumbers ?? Array.Empty<string>())
|
||||
.Concat(document.OrganizationNumbers ?? Array.Empty<string>())
|
||||
.Select(n => long.TryParse(n, out var number) ? number : -1);
|
||||
Log.Information("checking {personOrOrgNumber} on {document.DocumentId} in {personOrOrgNumbers} ignored", personOrOrgNumber, document.DocumentId, JsonSerializer.Serialize(personOrOrgNumbers));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CheckPersonOrOrgNumber
|
||||
{
|
||||
public static DocumentIsForMemberStrategy Strategy => DocumentIsForMember;
|
||||
|
||||
private static bool DocumentIsForMember(DocumentInformation document, long personOrOrgNumber) => (document.PersonNumbers ?? Array.Empty<string>())
|
||||
.Concat(document.OrganizationNumbers ?? Array.Empty<string>())
|
||||
.Select(n => long.TryParse(n, out var number) ? number : -1)
|
||||
.Contains(personOrOrgNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IBlobApiService
|
||||
{
|
||||
Task<byte[]> GetDocumentContent(Guid? documentBlobId);
|
||||
Task<Guid> UploadDocumentContent(byte[] content);
|
||||
Task UpdateDocumentContent(Guid? documentBlobId, byte[] content);
|
||||
Task DeleteDocumentContent(Guid? documentBlobId);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IDocumentAuditService
|
||||
{
|
||||
Task<List<DocumentAuditResponse>> GetDocumentAudits(params Guid[] documentIds);
|
||||
Task<IEnumerable<DocumentAudit>> CreateDocumentAudits(DocumentInformation oldDocumentInformation, DocumentInformation newDocumentInformation, bool wasDocumentContentUpdated, DateTime updateDate, string userId);
|
||||
public Task<IEnumerable<DocumentAudit>> CreateDocumentUploadedAudit(Guid documentId, DateTime uploadDate);
|
||||
public Task<IEnumerable<DocumentAudit>> CreateDocumentDeletedAudit(Guid documentId, DateTime deleteDate);
|
||||
public Task<IEnumerable<DocumentAudit>> CreateDocumentContentDeletedAudit(Guid documentId, DateTime deleteDate, string? reason);
|
||||
Task AuditAction(Guid documentId, string userId, string actionSource, DocumentAction action);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IDocumentInformationService
|
||||
{
|
||||
Task<DocumentInformation?> GetDocumentInformation(Guid documentId);
|
||||
Task<DocumentInformation?> GetDocumentInformationByBlobId(Guid? documentBlobId);
|
||||
Task DeleteDocumentInformation(Guid documentId);
|
||||
Task<DocumentInformation> UploadDocumentInformation(DocumentInformation information);
|
||||
Task<DocumentInformation?> UpdateDocumentInformation(Guid documentId, DocumentInformation information);
|
||||
Task<List<DocumentInformation>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null);
|
||||
Task<long> CountDocuments(DocumentInformationFilter filter);
|
||||
Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IDocumentService
|
||||
{
|
||||
Task<DocumentResponse?> GetDocument(Guid documentId);
|
||||
Task<DocumentResponse?> GetDocument(long personOrOrgNumber, Guid documentId);
|
||||
Task<byte[]?> GetDocumentByBlobId(Guid documentBlobId);
|
||||
Task<byte[]?> GetDocumentByBlobId(long personOrOrgNumber, Guid documentBlobId);
|
||||
Task<List<DocumentInformationResponse>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null);
|
||||
Task DeleteDocument(Guid documentId);
|
||||
Task DeleteDocumentContent(DeleteDocumentContentRequest request);
|
||||
Task<DocumentInformationResponse> UploadDocument(DocumentRequest request);
|
||||
Task<DocumentInformationResponse?> GetDocumentInformation(Guid documentId);
|
||||
Task<DocumentInformationResponse?> UpdateDocument(UpdateDocumentRequest request, Guid documentId, string userId);
|
||||
Task<DocumentInformationResponse?> UpdateDocumentContent(UpdateDocumentContentRequest request, Guid documentId, string userId);
|
||||
Task<DocumentInformationResponse?> UpdateDocumentInformation(UpdateDocumentInformationRequest request, Guid documentId, string userId);
|
||||
Task UpdateDocumentScanStatus(bool status, List<Guid> documentBlobIds, string userId);
|
||||
Task<long> CountDocuments(DocumentInformationFilter filter);
|
||||
Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services
|
||||
{
|
||||
public interface INotificationApiService
|
||||
{
|
||||
Task SendEmailAsync(EmailRequest emailRequest);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services
|
||||
{
|
||||
public class NotificationApiService : INotificationApiService
|
||||
{
|
||||
private readonly INotificationApiDao _notificationServiceApiDao;
|
||||
|
||||
public NotificationApiService(INotificationApiDao notificationApiServiceDao)
|
||||
{
|
||||
_notificationServiceApiDao = notificationApiServiceDao;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(EmailRequest emailRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _notificationServiceApiDao.SendEmailAsync(emailRequest);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Failure to send email. Subject: {emailRequest.Subject}; Recipients: {String.Join(", ", emailRequest.Recipient)}");
|
||||
throw new ServiceException($"Failure to send email. Subject: {emailRequest.Subject}; Recipients: {String.Join(", ", emailRequest.Recipient)}; Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"AzureAd": {
|
||||
"ClientId": "c92072ab-987f-4b67-9923-730e06b2766f",
|
||||
"Instance": "https://login.microsoftonline.com/",
|
||||
"Domain": "landmarkcu.onmicrosoft.com",
|
||||
"TenantId": "63cd1554-2783-4a86-ae50-b9e6c21c9499",
|
||||
"CallbackPath": "/signin-oidc"
|
||||
},
|
||||
"AzureKeyVaultUrl": "qa-DocumentApi-kv",
|
||||
"ClientId": "c92072ab-987f-4b67-9923-730e06b2766f",
|
||||
"ClientSecret": "OjS8Q~TuByD~pylYMu5pbw9UGNG5fldT6eP9YcBI",
|
||||
"TenantId": "63cd1554-2783-4a86-ae50-b9e6c21c9499",
|
||||
"Swagger": {
|
||||
"OAuthScope": "api://c92072ab-987f-4b67-9923-730e06b2766f/user.groups"
|
||||
},
|
||||
"Primary": {
|
||||
"AppName": "Document Api",
|
||||
"SeqUrl": "https://qa-seq.lcu-internal.com",
|
||||
"SeqApiKey": "ftd9RlWVEI1ctg3dqusa",
|
||||
"FileUploadCutoffHours": -2,
|
||||
"IsAksDeploy": "false",
|
||||
"CorsAllowedOrigins": [
|
||||
"http://lcu-internal.com",
|
||||
"https://lcu-internal.com",
|
||||
"https://qa-api.lcu-internal.com"
|
||||
],
|
||||
"MaliciousFileFoundEmailList": [
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
]
|
||||
},
|
||||
"BlobStorage": {
|
||||
"connectionString": "VALUE-IN-KEYVAULT",
|
||||
"containerName": "documents"
|
||||
},
|
||||
"MongoDatabase": {
|
||||
"connectionString": "VALUE-IN-KEYVAULT",
|
||||
"databaseName": "DocumentsApi",
|
||||
"documentContainerName": "DocumentsInformation",
|
||||
"auditContainerName": "AuditInformation"
|
||||
},
|
||||
"ApiSecurityConfiguration": {
|
||||
"ApiKeyAuthenticationFailureBehavior": "Error",
|
||||
"ExternalApiKeySet": {
|
||||
"DocumentTypeUploader": "VALUE-IN-KEYVAULT",
|
||||
"DocumentTypeRetriever": "VALUE-IN-KEYVAULT",
|
||||
"DocumentTypeCleanup": "VALUE-IN-KEYVAULT"
|
||||
},
|
||||
"InternalApiKeySet": {}
|
||||
},
|
||||
"NotificationApiConfiguration": {
|
||||
"Url": "https://qa-notificationsapi.lcu-internal.com",
|
||||
"Email": "/api/Email"
|
||||
},
|
||||
"FeatureManagement": {
|
||||
"GetDocumentCheckOwnership": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user