diff --git a/source/backend/Pims.Scheduler.Test/Pims.Scheduler.Test.csproj b/source/backend/Pims.Scheduler.Test/Pims.Scheduler.Test.csproj
new file mode 100644
index 0000000000..2ec4df52a3
--- /dev/null
+++ b/source/backend/Pims.Scheduler.Test/Pims.Scheduler.Test.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/source/backend/Pims.Scheduler.Test/Repositories/PimsDocumentQueueRepositoryTests.cs b/source/backend/Pims.Scheduler.Test/Repositories/PimsDocumentQueueRepositoryTests.cs
new file mode 100644
index 0000000000..6083026ef6
--- /dev/null
+++ b/source/backend/Pims.Scheduler.Test/Repositories/PimsDocumentQueueRepositoryTests.cs
@@ -0,0 +1,105 @@
+using FluentAssertions;
+using Moq;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
+using Pims.Dal.Entities.Models;
+using Pims.Scheduler.Repositories;
+using Xunit;
+
+namespace Pims.Scheduler.Test.Repositories
+{
+ public class PimsDocumentQueueRepositoryTest
+ {
+ [Fact]
+ public async Task PollQueuedDocument_ValidDocument_ReturnsExternalResponse()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1 };
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.PollQueuedDocument(document)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.PollQueuedDocument(document);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.PollQueuedDocument(document), Times.Once);
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocument_ValidDocument_ReturnsExternalResponse()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1 };
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.UploadQueuedDocument(document);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.UploadQueuedDocument(document), Times.Once);
+ }
+
+ [Fact]
+ public async Task UpdateQueuedDocument_ValidDocument_ReturnsExternalResponse()
+ {
+ // Arrange
+ var documentQueueId = 1;
+ var document = new DocumentQueueModel { Id = documentQueueId };
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.UpdateQueuedDocument(documentQueueId, document)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.UpdateQueuedDocument(documentQueueId, document);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.UpdateQueuedDocument(documentQueueId, document), Times.Once);
+ }
+
+ [Fact]
+ public async Task SearchQueuedDocumentsAsync_ValidFilter_ReturnsExternalResponse()
+ {
+ // Arrange
+ var filter = new DocumentQueueFilter();
+ var expectedResponse = new ExternalResponse> { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(filter)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.SearchQueuedDocumentsAsync(filter);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.SearchQueuedDocumentsAsync(filter), Times.Once);
+ }
+
+ [Fact]
+ public async Task GetById_ValidDocumentQueueId_ReturnsExternalResponse()
+ {
+ // Arrange
+ var documentQueueId = 1;
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.GetById(documentQueueId)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.GetById(documentQueueId);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.GetById(documentQueueId), Times.Once);
+ }
+ }
+}
diff --git a/source/backend/Pims.Scheduler.Test/Services/DocumentQueueServiceTests.cs b/source/backend/Pims.Scheduler.Test/Services/DocumentQueueServiceTests.cs
new file mode 100644
index 0000000000..fe54a96092
--- /dev/null
+++ b/source/backend/Pims.Scheduler.Test/Services/DocumentQueueServiceTests.cs
@@ -0,0 +1,303 @@
+using FluentAssertions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Moq;
+using Pims.Api.Models.Base;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
+using Pims.Dal.Entities.Models;
+using Pims.Scheduler.Http.Configuration;
+using Pims.Scheduler.Models;
+using Pims.Scheduler.Repositories;
+using Pims.Scheduler.Services;
+using Xunit;
+
+namespace Pims.Scheduler.Test.Services
+{
+ public class DocumentQueueServiceTests
+ {
+ private readonly Mock> _loggerMock;
+ private readonly Mock _documentQueueRepositoryMock;
+ private readonly Mock> _uploadOptionsMock;
+ private readonly Mock> _queryOptionsMock;
+ private readonly Mock> _retryOptionsMock;
+ private readonly DocumentQueueService _service;
+
+ public DocumentQueueServiceTests()
+ {
+ _loggerMock = new Mock>();
+ _documentQueueRepositoryMock = new Mock();
+ _uploadOptionsMock = new Mock>();
+ _queryOptionsMock = new Mock>();
+ _retryOptionsMock = new Mock>();
+ _uploadOptionsMock.Setup(x => x.CurrentValue).Returns(new UploadQueuedDocumentsJobOptions() { BatchSize = 10, MaxFileSize = 100 });
+ _queryOptionsMock.Setup(x => x.CurrentValue).Returns(new QueryProcessingDocumentsJobOptions() { BatchSize = 10, MaxProcessingMinutes = 100 });
+
+ _service = new DocumentQueueService(
+ _loggerMock.Object,
+ _uploadOptionsMock.Object,
+ _queryOptionsMock.Object,
+ _retryOptionsMock.Object,
+ _documentQueueRepositoryMock.Object
+ );
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_NoDocumentsToProcess_ReturnsSkipped()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Success, Payload = new List() };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SKIPPED);
+ result.Message.Should().Be("No documents to process, skipping execution.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_ErrorStatus_ReturnsError()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Error, Message = "Error", Payload = new List() { new DocumentQueueModel() } };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.Message.Should().Be("Received error status from pims document queue service, aborting.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_SingleDocumentError_ReturnsError()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error uploading document.",
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Received error response from UploadQueuedDocument for queued document 1 status Error message: Error uploading document.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_SingleDocumentError_ReturnsError_UpdatesQueue()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.GetById(It.IsAny())).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = document,
+ });
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error uploading document.",
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Received error response from UploadQueuedDocument for queued document 1 status Error message: Error uploading document.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_SingleDocumentSuccess_ReturnsSuccess()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = document,
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SUCCESS);
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_TwoDocumentsMixedResults_ReturnsPartialSuccess()
+ {
+ // Arrange
+ var document1 = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var document2 = new DocumentQueueModel { Id = 2, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document1, document2 },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document1)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = document1,
+ });
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document2)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error uploading document 2.",
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.PARTIAL);
+ result.DocumentQueueResponses.Should().HaveCount(2);
+ result.DocumentQueueResponses.ToArray()[1].Message.Should().Be("Received error response from UploadQueuedDocument for queued document 2 status Error message: Error uploading document 2.");
+ }
+
+ [Fact]
+ public async Task RetryQueuedDocuments_ErrorStatus_ReturnsError()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Error, Message = "Error", Payload = new List() { new DocumentQueueModel() } };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.RetryQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.Message.Should().Be("Received error status from pims document queue service, aborting.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_NoDocumentsToProcess_ReturnsSkipped()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Success, Payload = new List() };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SKIPPED);
+ result.Message.Should().Be("No documents to process, skipping execution.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_ErrorStatus_ReturnsError()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Error, Message = "Error", Payload = new List() { new DocumentQueueModel() } };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.Message.Should().Be("Received error status from pims document queue service, aborting.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_OneDocumentError_ReturnsError()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.PollQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error processing document.",
+ });
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Received error response from PollQueuedDocument for queued document 1 status Error message: Error processing document.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_OneDocumentSuccess_ReturnsSuccess()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.PollQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new DocumentQueueModel { Id = document.Id, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.SUCCESS.ToString() } },
+ });
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SUCCESS);
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_OneDocumentExceededMaxProcessingTime_ReturnsError()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() }, DocumentProcessStartTimestamp = DateTime.UtcNow.AddDays(-2) };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Document processing for document 1 has exceeded maximum processing time of 100");
+ }
+
+
+ }
+}
diff --git a/source/backend/api/Areas/Documents/DocumentQueueController.cs b/source/backend/api/Areas/Documents/DocumentQueueController.cs
index 9fead6a65e..3a3fd79efa 100644
--- a/source/backend/api/Areas/Documents/DocumentQueueController.cs
+++ b/source/backend/api/Areas/Documents/DocumentQueueController.cs
@@ -1,11 +1,18 @@
+using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using MapsterMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Pims.Api.Models.Concepts.Document;
using Pims.Api.Services;
+using Pims.Core.Api.Exceptions;
using Pims.Core.Api.Policies;
+using Pims.Core.Extensions;
using Pims.Core.Json;
using Pims.Core.Security;
+using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
using Swashbuckle.AspNetCore.Annotations;
@@ -18,12 +25,13 @@ namespace Pims.Api.Controllers
[ApiController]
[ApiVersion("1.0")]
[Route("v{version:apiVersion}/documents/queue")]
- [Route("/documents")]
+ [Route("/documents/queue")]
public class DocumentQueueController : ControllerBase
{
#region Variables
private readonly IDocumentQueueService _documentQueueService;
private readonly IMapper _mapper;
+ private readonly ILogger _logger;
#endregion
#region Constructors
@@ -33,32 +41,155 @@ public class DocumentQueueController : ControllerBase
///
///
///
- public DocumentQueueController(IDocumentQueueService documentQueueService, IMapper mapper)
+ ///
+ public DocumentQueueController(IDocumentQueueService documentQueueService, IMapper mapper, ILogger logger)
{
_documentQueueService = documentQueueService;
_mapper = mapper;
+ _logger = logger;
}
#endregion
#region Endpoints
+ ///
+ /// Update a Queued Document.
+ ///
+ ///
+ [HttpPut("{documentQueueId:long}")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public IActionResult Update(long documentQueueId, [FromBody] DocumentQueueModel documentQueue)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(Update),
+ User.GetUsername(),
+ DateTime.Now);
+
+ documentQueue.ThrowIfNull(nameof(documentQueue));
+ if (documentQueueId != documentQueue.Id)
+ {
+ throw new BadRequestException("Invalid document queue id.");
+ }
+
+ var queuedDocuments = _documentQueueService.Update(_mapper.Map(documentQueue));
+ var updatedDocumentQueue = _mapper.Map(queuedDocuments);
+ return new JsonResult(updatedDocumentQueue);
+ }
+
+ ///
+ /// Poll a queud document to check on the upload status.
+ ///
+ ///
+ [HttpPost("{documentQueueId:long}/poll")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public async Task Poll(long documentQueueId, [FromBody] DocumentQueueModel documentQueue)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(Poll),
+ User.GetUsername(),
+ DateTime.Now);
+
+ documentQueue.ThrowIfNull(nameof(documentQueue));
+ if (documentQueueId != documentQueue.Id)
+ {
+ throw new BadRequestException("Invalid document queue id.");
+ }
+
+ var queuedDocuments = await _documentQueueService.PollForDocument(_mapper.Map(documentQueue));
+ var updatedDocumentQueue = _mapper.Map(queuedDocuments);
+ return new JsonResult(updatedDocumentQueue);
+ }
+
+ ///
+ /// Upload a Queued Document.
+ ///
+ ///
+ [HttpPost("{documentQueueId:long}/upload")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public async Task Upload(long documentQueueId, [FromBody] DocumentQueueModel documentQueue)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(Upload),
+ User.GetUsername(),
+ DateTime.Now);
+
+ documentQueue.ThrowIfNull(nameof(documentQueue));
+ if (documentQueueId != documentQueue.Id)
+ {
+ throw new BadRequestException("Invalid document queue id.");
+ }
+
+ var queuedDocuments = await _documentQueueService.Upload(_mapper.Map(documentQueue));
+ var updatedDocumentQueue = _mapper.Map(queuedDocuments);
+ return new JsonResult(updatedDocumentQueue);
+ }
+
///
/// Search for Document Queue items via filter.
///
///
- [HttpGet("search")]
+ [HttpPost("search")]
[HasPermission(Permissions.SystemAdmin)]
[Produces("application/json")]
- [ProducesResponseType(typeof(List), 200)]
+ [ProducesResponseType(typeof(List), 200)]
[SwaggerOperation(Tags = new[] { "document-types" })]
[TypeFilter(typeof(NullJsonResultFilter))]
- public IActionResult GetDocumentTypes([FromBody] DocumentQueueFilter filter)
+ public IActionResult SearchQueuedDocuments([FromBody] DocumentQueueFilter filter)
{
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(SearchQueuedDocuments),
+ User.GetUsername(),
+ DateTime.Now);
+
var queuedDocuments = _documentQueueService.SearchDocumentQueue(filter);
- var documentQueueModels = _mapper.Map>(queuedDocuments);
+ var documentQueueModels = _mapper.Map>(queuedDocuments);
return new JsonResult(documentQueueModels);
}
+ ///
+ /// Get Document Queue item via id.
+ ///
+ ///
+ [HttpGet("{documentQueueId:long}")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public IActionResult GetQueuedDocument(long documentQueueId)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(GetQueuedDocument),
+ User.GetUsername(),
+ DateTime.Now);
+
+ var queuedDocuments = _documentQueueService.GetById(documentQueueId);
+ var documentQueueModel = _mapper.Map(queuedDocuments);
+ return new JsonResult(documentQueueModel);
+ }
+
#endregion
}
}
diff --git a/source/backend/api/Areas/Documents/DocumentRelationshipController.cs b/source/backend/api/Areas/Documents/DocumentRelationshipController.cs
index 5202ab1026..e0ca25a724 100644
--- a/source/backend/api/Areas/Documents/DocumentRelationshipController.cs
+++ b/source/backend/api/Areas/Documents/DocumentRelationshipController.cs
@@ -4,15 +4,15 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Pims.Api.Constants;
-using Pims.Core.Api.Exceptions;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Concepts.Document;
using Pims.Api.Models.Requests.Document.Upload;
-using Pims.Core.Api.Policies;
using Pims.Api.Services;
+using Pims.Core.Api.Exceptions;
+using Pims.Core.Api.Policies;
using Pims.Core.Json;
-using Pims.Dal.Entities;
using Pims.Core.Security;
+using Pims.Dal.Entities;
using Swashbuckle.AspNetCore.Annotations;
namespace Pims.Api.Controllers
@@ -142,19 +142,27 @@ public async Task UploadDocumentWithParent(
string parentId,
[FromForm] DocumentUploadRequest uploadRequest)
{
- var response = relationshipType switch
+ switch (relationshipType)
{
- DocumentRelationType.AcquisitionFiles => await _documentFileService.UploadAcquisitionDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.ResearchFiles => await _documentFileService.UploadResearchDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.Templates => await _formDocumentService.UploadFormDocumentTemplateAsync(parentId, uploadRequest),
- DocumentRelationType.Projects => await _documentFileService.UploadProjectDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.Leases => await _documentFileService.UploadLeaseDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.ManagementFiles => await _documentFileService.UploadPropertyActivityDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.DispositionFiles => await _documentFileService.UploadDispositionDocumentAsync(long.Parse(parentId), uploadRequest),
- _ => throw new BadRequestException("Relationship type not valid for upload."),
- };
+ case DocumentRelationType.AcquisitionFiles:
+ await _documentFileService.UploadAcquisitionDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.ResearchFiles:
+ await _documentFileService.UploadResearchDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.Projects:
+ await _documentFileService.UploadProjectDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.Leases:
+ await _documentFileService.UploadLeaseDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.ManagementFiles:
+ await _documentFileService.UploadPropertyActivityDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.DispositionFiles:
+ await _documentFileService.UploadDispositionDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.Templates:
+ await _formDocumentService.UploadFormDocumentTemplateAsync(parentId, uploadRequest); break;
+ default:
+ throw new BadRequestException("Relationship type not valid for upload.");
+ }
- return new JsonResult(response);
+ return Ok();
}
///
@@ -169,7 +177,7 @@ public async Task UploadDocumentWithParent(
[ProducesResponseType(typeof(bool), 200)]
[SwaggerOperation(Tags = new[] { "document" })]
[TypeFilter(typeof(NullJsonResultFilter))]
- public async Task DeleteDocumentRelationship(DocumentRelationType relationshipType, [FromBody] DocumentRelationshipModel model)
+ public async Task DeleteDocumentRelationship([FromRoute]DocumentRelationType relationshipType, [FromBody]DocumentRelationshipModel model)
{
switch (relationshipType)
{
diff --git a/source/backend/api/Helpers/Extensions/DocumentUploadRequestExtension.cs b/source/backend/api/Helpers/Extensions/DocumentUploadRequestExtension.cs
new file mode 100644
index 0000000000..23a8cef1b7
--- /dev/null
+++ b/source/backend/api/Helpers/Extensions/DocumentUploadRequestExtension.cs
@@ -0,0 +1,21 @@
+using System;
+using Pims.Api.Models.Requests.Document.Upload;
+using Pims.Core.Api.Exceptions;
+
+namespace Pims.Api.Helpers.Extensions
+{
+ public static class DocumentUploadRequestExtension
+ {
+ public static void ThrowInvalidFileSize(this DocumentUploadRequest documentUploadRequest)
+ {
+ ArgumentNullException.ThrowIfNull(documentUploadRequest);
+
+ if (documentUploadRequest.File is not null && documentUploadRequest.File.Length == 0)
+ {
+ throw new BadRequestException("The submitted file is empty");
+ }
+
+ return;
+ }
+ }
+}
diff --git a/source/backend/api/Helpers/Extensions/FormFileExtensions.cs b/source/backend/api/Helpers/Extensions/FormFileExtensions.cs
new file mode 100644
index 0000000000..d7b7d0cbe4
--- /dev/null
+++ b/source/backend/api/Helpers/Extensions/FormFileExtensions.cs
@@ -0,0 +1,17 @@
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Pims.Api.Helpers.Extensions
+{
+ public static class FormFileExtensions
+ {
+ public static async Task GetBytes(this IFormFile formFile)
+ {
+ using var memoryStream = new MemoryStream();
+ await formFile.CopyToAsync(memoryStream);
+
+ return memoryStream.ToArray();
+ }
+ }
+}
diff --git a/source/backend/api/Pims.Api.csproj b/source/backend/api/Pims.Api.csproj
index 4fd7e03de3..0d1ee769ad 100644
--- a/source/backend/api/Pims.Api.csproj
+++ b/source/backend/api/Pims.Api.csproj
@@ -2,7 +2,7 @@
0ef6255f-9ea0-49ec-8c65-c172304b4926
- 5.7.0-96.7
+ 5.7.0-96.11
5.7.0.96
true
{16BC0468-78F6-4C91-87DA-7403C919E646}
diff --git a/source/backend/api/Pims.sln b/source/backend/api/Pims.sln
index 0a83937ba1..051693cbc0 100644
--- a/source/backend/api/Pims.sln
+++ b/source/backend/api/Pims.sln
@@ -44,15 +44,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A0343C94-4
docs\VERSIONING.md = docs\VERSIONING.md
EndProjectSection
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "..\tests", "{F256F2A5-0DBF-4137-A7D6-21F08111BD4A}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F256F2A5-0DBF-4137-A7D6-21F08111BD4A}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit", "..\unit", "{3D70B211-74A8-484C-9B86-B0A2835C71E7}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit", "unit", "{3D70B211-74A8-484C-9B86-B0A2835C71E7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Api.Test", "..\tests\unit\api\Pims.Api.Test.csproj", "{1F4E301C-F03B-4A31-A6F2-6A77384A74DA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Dal.Test", "..\tests\unit\dal\Pims.Dal.Test.csproj", "{412BF533-2759-4FBE-B4C6-B89DB44FB6B5}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "..\core", "{04780892-FC30-4B6B-A10C-5795C657E574}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{04780892-FC30-4B6B-A10C-5795C657E574}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Core.Test", "..\tests\core\Pims.Core.Test.csproj", "{5A83C636-741A-4795-8588-70F033E79B5A}"
EndProject
@@ -66,6 +66,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Av", "..\clamav\Pims.A
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Core.Api", "..\core.api\Pims.Core.Api.csproj", "{89A99CC5-ADFB-4FC2-9136-7B0029EEA2D8}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pims.Scheduler.Test", "..\Pims.Scheduler.Test\Pims.Scheduler.Test.csproj", "{6B20887E-B784-4D78-939B-BDD8206DBE17}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Scheduler", "..\scheduler\Pims.Scheduler.csproj", "{AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -256,6 +260,30 @@ Global
{89A99CC5-ADFB-4FC2-9136-7B0029EEA2D8}.Release|x64.Build.0 = Release|Any CPU
{89A99CC5-ADFB-4FC2-9136-7B0029EEA2D8}.Release|x86.ActiveCfg = Release|Any CPU
{89A99CC5-ADFB-4FC2-9136-7B0029EEA2D8}.Release|x86.Build.0 = Release|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Debug|x64.Build.0 = Debug|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Debug|x86.Build.0 = Debug|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Release|x64.ActiveCfg = Release|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Release|x64.Build.0 = Release|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Release|x86.ActiveCfg = Release|Any CPU
+ {6B20887E-B784-4D78-939B-BDD8206DBE17}.Release|x86.Build.0 = Release|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Debug|x64.Build.0 = Debug|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Debug|x86.Build.0 = Debug|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Release|x64.ActiveCfg = Release|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Release|x64.Build.0 = Release|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Release|x86.ActiveCfg = Release|Any CPU
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -276,6 +304,8 @@ Global
{2C31E92C-9C95-45FF-9F95-928C2962F37D} = {3D70B211-74A8-484C-9B86-B0A2835C71E7}
{16C06BDA-112F-4D04-82FF-0BBE45072372} = {5237F8A4-67F5-4751-B8B2-B93A06791480}
{89A99CC5-ADFB-4FC2-9136-7B0029EEA2D8} = {5237F8A4-67F5-4751-B8B2-B93A06791480}
+ {6B20887E-B784-4D78-939B-BDD8206DBE17} = {3D70B211-74A8-484C-9B86-B0A2835C71E7}
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F} = {5237F8A4-67F5-4751-B8B2-B93A06791480}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3433C5DD-DC49-4A96-A1AE-90C1A1EBA87C}
diff --git a/source/backend/api/Repositories/Cdogs/CdogsAuthRepository.cs b/source/backend/api/Repositories/Cdogs/CdogsAuthRepository.cs
index f86a032277..d2bf249d70 100644
--- a/source/backend/api/Repositories/Cdogs/CdogsAuthRepository.cs
+++ b/source/backend/api/Repositories/Cdogs/CdogsAuthRepository.cs
@@ -1,13 +1,15 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
+using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
-using Pims.Core.Api.Exceptions;
+using Microsoft.Extensions.Options;
using Pims.Api.Models.Cdogs;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Requests.Http;
+using Pims.Core.Api.Exceptions;
namespace Pims.Api.Repositories.Cdogs
{
@@ -25,11 +27,13 @@ public class CdogsAuthRepository : CdogsBaseRepository, IDocumentGenerationAuthR
/// Injected Logger Provider.
/// Injected Httpclient factory.
/// The injected configuration provider.
+ /// The jsonOptions.
public CdogsAuthRepository(
ILogger logger,
IHttpClientFactory httpClientFactory,
- IConfiguration configuration)
- : base(logger, httpClientFactory, configuration)
+ IConfiguration configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, configuration, jsonOptions)
{
_currentToken = null;
_lastSucessfullRequest = DateTime.UnixEpoch;
diff --git a/source/backend/api/Repositories/Cdogs/CdogsBaseRepository.cs b/source/backend/api/Repositories/Cdogs/CdogsBaseRepository.cs
index d1d192b1d5..185199adc0 100644
--- a/source/backend/api/Repositories/Cdogs/CdogsBaseRepository.cs
+++ b/source/backend/api/Repositories/Cdogs/CdogsBaseRepository.cs
@@ -1,7 +1,9 @@
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models.Config;
using Pims.Core.Api.Repositories.Rest;
@@ -21,11 +23,13 @@ public abstract class CdogsBaseRepository : BaseRestRepository
/// Injected Logger Provider.
/// Injected Httpclient factory.
/// The injected configuration provider.
+ /// The json options.
protected CdogsBaseRepository(
ILogger logger,
IHttpClientFactory httpClientFactory,
- IConfiguration configuration)
- : base(logger, httpClientFactory)
+ IConfiguration configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, jsonOptions)
{
_config = new CdogsConfig();
configuration.Bind(CdogsConfigSectionKey, _config);
diff --git a/source/backend/api/Repositories/Cdogs/CdogsRepository.cs b/source/backend/api/Repositories/Cdogs/CdogsRepository.cs
index 435f0e4c8b..7d3a36d51e 100644
--- a/source/backend/api/Repositories/Cdogs/CdogsRepository.cs
+++ b/source/backend/api/Repositories/Cdogs/CdogsRepository.cs
@@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models.Cdogs;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Requests.Http;
@@ -30,12 +31,14 @@ public class CdogsRepository : CdogsBaseRepository, IDocumentGenerationRepositor
/// Injected Httpclient factory.
/// Injected repository that handles authentication.
/// The injected configuration provider.
+ /// The jsonOptions.
public CdogsRepository(
ILogger logger,
IHttpClientFactory httpClientFactory,
IDocumentGenerationAuthRepository authRepository,
- IConfiguration configuration)
- : base(logger, httpClientFactory, configuration)
+ IConfiguration configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, configuration, jsonOptions)
{
_authRepository = authRepository;
}
diff --git a/source/backend/api/Repositories/Mayan/MayanAuthRepository.cs b/source/backend/api/Repositories/Mayan/MayanAuthRepository.cs
index 4f534fdc29..952ba6b4d4 100644
--- a/source/backend/api/Repositories/Mayan/MayanAuthRepository.cs
+++ b/source/backend/api/Repositories/Mayan/MayanAuthRepository.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Mayan;
using Pims.Api.Models.Requests.Http;
@@ -26,11 +27,13 @@ public class MayanAuthRepository : MayanBaseRepository, IEdmsAuthRepository
/// Injected Logger Provider.
/// Injected Httpclient factory.
/// The injected configuration provider.
+ /// The jsonOptions.
public MayanAuthRepository(
ILogger logger,
IHttpClientFactory httpClientFactory,
- IConfiguration configuration)
- : base(logger, httpClientFactory, configuration)
+ IConfiguration configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, configuration, jsonOptions)
{
_currentToken = string.Empty;
}
diff --git a/source/backend/api/Repositories/Mayan/MayanBaseRepository.cs b/source/backend/api/Repositories/Mayan/MayanBaseRepository.cs
index ab06e068fc..a57678618a 100644
--- a/source/backend/api/Repositories/Mayan/MayanBaseRepository.cs
+++ b/source/backend/api/Repositories/Mayan/MayanBaseRepository.cs
@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models.Config;
using Pims.Core.Api.Repositories.Rest;
@@ -22,11 +24,13 @@ public abstract class MayanBaseRepository : BaseRestRepository
/// Injected Logger Provider.
/// Injected Httpclient factory.
/// The injected configuration provider.
+ /// The injected json options.
protected MayanBaseRepository(
ILogger logger,
IHttpClientFactory httpClientFactory,
- IConfiguration configuration)
- : base(logger, httpClientFactory)
+ IConfiguration configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, jsonOptions)
{
_config = new MayanConfig();
configuration.Bind(MayanConfigSectionKey, _config);
diff --git a/source/backend/api/Repositories/Mayan/MayanDocumentRepository.cs b/source/backend/api/Repositories/Mayan/MayanDocumentRepository.cs
index 89fb6921a3..72a3315968 100644
--- a/source/backend/api/Repositories/Mayan/MayanDocumentRepository.cs
+++ b/source/backend/api/Repositories/Mayan/MayanDocumentRepository.cs
@@ -11,6 +11,7 @@
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Mayan;
@@ -35,12 +36,14 @@ public class MayanDocumentRepository : MayanBaseRepository, IEdmsDocumentReposit
/// Injected Httpclient factory.
/// Injected repository that handles authentication.
/// The injected configuration provider.
+ /// The jsonOptions.
public MayanDocumentRepository(
ILogger logger,
IHttpClientFactory httpClientFactory,
IEdmsAuthRepository authRepository,
- IConfiguration configuration)
- : base(logger, httpClientFactory, configuration)
+ IConfiguration configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, configuration, jsonOptions)
{
_authRepository = authRepository;
}
diff --git a/source/backend/api/Repositories/Mayan/MayanMetadataRepository.cs b/source/backend/api/Repositories/Mayan/MayanMetadataRepository.cs
index 24e77a0da3..9ecaa04300 100644
--- a/source/backend/api/Repositories/Mayan/MayanMetadataRepository.cs
+++ b/source/backend/api/Repositories/Mayan/MayanMetadataRepository.cs
@@ -7,6 +7,7 @@
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models.Mayan;
using Pims.Api.Models.Mayan.Metadata;
using Pims.Api.Models.Requests.Http;
@@ -28,12 +29,14 @@ public class MayanMetadataRepository : MayanBaseRepository, IEdmsMetadataReposit
/// Injected Httpclient factory.
/// Injected repository that handles authentication.
/// The injected configuration provider.
+ /// The json options.
public MayanMetadataRepository(
ILogger logger,
IHttpClientFactory httpClientFactory,
IEdmsAuthRepository authRepository,
- IConfiguration configuration)
- : base(logger, httpClientFactory, configuration)
+ IConfiguration configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, configuration, jsonOptions)
{
_authRepository = authRepository;
}
diff --git a/source/backend/api/Services/DocumentFileService.cs b/source/backend/api/Services/DocumentFileService.cs
index 098e3e794c..55dd68affa 100644
--- a/source/backend/api/Services/DocumentFileService.cs
+++ b/source/backend/api/Services/DocumentFileService.cs
@@ -1,12 +1,14 @@
+using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using System.Security.Claims;
+using System.Text.Json;
using System.Threading.Tasks;
-using MapsterMapper;
using Microsoft.Extensions.Logging;
using Pims.Api.Constants;
+using Pims.Api.Helpers.Extensions;
using Pims.Api.Models.CodeTypes;
-using Pims.Api.Models.Concepts.Document;
using Pims.Api.Models.Requests.Document.Upload;
using Pims.Api.Models.Requests.Http;
using Pims.Core.Api.Exceptions;
@@ -23,15 +25,15 @@ namespace Pims.Api.Services
///
public class DocumentFileService : BaseService, IDocumentFileService
{
- private readonly IAcquisitionFileDocumentRepository acquisitionFileDocumentRepository;
- private readonly IResearchFileDocumentRepository researchFileDocumentRepository;
- private readonly IDocumentService documentService;
+ private readonly IAcquisitionFileDocumentRepository _acquisitionFileDocumentRepository;
+ private readonly IResearchFileDocumentRepository _researchFileDocumentRepository;
+ private readonly IDocumentService _documentService;
private readonly IProjectRepository _projectRepository;
private readonly IDocumentRepository _documentRepository;
+ private readonly IDocumentQueueRepository _documentQueueRepository;
private readonly ILeaseRepository _leaseRepository;
private readonly IPropertyActivityDocumentRepository _propertyActivityDocumentRepository;
private readonly IDispositionFileDocumentRepository _dispositionFileDocumentRepository;
- private readonly IMapper mapper;
public DocumentFileService(
ClaimsPrincipal user,
@@ -39,23 +41,23 @@ public DocumentFileService(
IAcquisitionFileDocumentRepository acquisitionFileDocumentRepository,
IResearchFileDocumentRepository researchFileDocumentRepository,
IDocumentService documentService,
- IMapper mapper,
IProjectRepository projectRepository,
IDocumentRepository documentRepository,
ILeaseRepository leaseRepository,
IPropertyActivityDocumentRepository propertyActivityDocumentRepository,
- IDispositionFileDocumentRepository dispositionFileDocumentRepository)
+ IDispositionFileDocumentRepository dispositionFileDocumentRepository,
+ IDocumentQueueRepository documentQueueRepository)
: base(user, logger)
{
- this.acquisitionFileDocumentRepository = acquisitionFileDocumentRepository;
- this.researchFileDocumentRepository = researchFileDocumentRepository;
- this.documentService = documentService;
- this.mapper = mapper;
+ _acquisitionFileDocumentRepository = acquisitionFileDocumentRepository;
+ _researchFileDocumentRepository = researchFileDocumentRepository;
+ _documentService = documentService;
_projectRepository = projectRepository;
_documentRepository = documentRepository;
_leaseRepository = leaseRepository;
_propertyActivityDocumentRepository = propertyActivityDocumentRepository;
_dispositionFileDocumentRepository = dispositionFileDocumentRepository;
+ _documentQueueRepository = documentQueueRepository;
}
public IList GetFileDocuments(FileType fileType, long fileId)
@@ -68,10 +70,10 @@ public IList GetFileDocuments(FileType fileType, long fileId)
{
case FileType.Research:
User.ThrowIfNotAuthorized(Permissions.ResearchFileView);
- return researchFileDocumentRepository.GetAllByResearchFile(fileId).Select(f => f as T).ToArray();
+ return _researchFileDocumentRepository.GetAllByResearchFile(fileId).Select(f => f as T).ToArray();
case FileType.Acquisition:
User.ThrowIfNotAuthorized(Permissions.AcquisitionFileView);
- return acquisitionFileDocumentRepository.GetAllByAcquisitionFile(fileId).Select(f => f as T).ToArray();
+ return _acquisitionFileDocumentRepository.GetAllByAcquisitionFile(fileId).Select(f => f as T).ToArray();
case FileType.Project:
User.ThrowIfNotAuthorized(Permissions.ProjectView);
return _projectRepository.GetAllProjectDocuments(fileId).Select(f => f as T).ToArray();
@@ -89,210 +91,136 @@ public IList GetFileDocuments(FileType fileType, long fileId)
}
}
- public async Task UploadResearchDocumentAsync(long researchFileId, DocumentUploadRequest uploadRequest)
+ public async Task UploadAcquisitionDocument(long acquisitionFileId, DocumentUploadRequest uploadRequest)
{
- Logger.LogInformation("Uploading document for single research file");
- User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ResearchFileEdit);
-
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
+ Logger.LogInformation("Uploading document for single acquisition file");
+ User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.AcquisitionFileEdit);
+ uploadRequest.ThrowInvalidFileSize();
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new DocumentUploadRelationshipResponse()
+ PimsAcquisitionFileDocument newAcquisitionDocument = new()
{
- UploadResponse = uploadResult,
+ AcquisitionFileId = acquisitionFileId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _acquisitionFileDocumentRepository.AddAcquisition(newAcquisitionDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _acquisitionFileDocumentRepository.CommitTransaction();
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- // Create the pims document research file relationship
- PimsResearchFileDocument newResearchFileDocument = new PimsResearchFileDocument()
- {
- ResearchFileId = researchFileId,
- DocumentId = uploadResult.Document.Id,
- };
- newResearchFileDocument = researchFileDocumentRepository.AddResearch(newResearchFileDocument);
- researchFileDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newResearchFileDocument);
- }
-
- return relationshipResponse;
+ return;
}
- public async Task UploadAcquisitionDocumentAsync(long acquisitionFileId, DocumentUploadRequest uploadRequest)
+ public async Task UploadResearchDocument(long researchFileId, DocumentUploadRequest uploadRequest)
{
- Logger.LogInformation("Uploading document for single acquisition file");
- User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.AcquisitionFileEdit);
-
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
+ Logger.LogInformation("Uploading document for single research file");
+ User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ResearchFileEdit);
+ uploadRequest.ThrowInvalidFileSize();
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new DocumentUploadRelationshipResponse()
+ PimsResearchFileDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ ResearchFileId = researchFileId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _researchFileDocumentRepository.AddResearch(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- // Create the pims document acquisition file relationship
- PimsAcquisitionFileDocument newAcquisitionDocument = new PimsAcquisitionFileDocument()
- {
- AcquisitionFileId = acquisitionFileId,
- DocumentId = uploadResult.Document.Id,
- };
- newAcquisitionDocument = acquisitionFileDocumentRepository.AddAcquisition(newAcquisitionDocument);
- acquisitionFileDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newAcquisitionDocument);
- }
-
- return relationshipResponse;
+ return;
}
- public async Task UploadProjectDocumentAsync(long projectId, DocumentUploadRequest uploadRequest)
+ public async Task UploadProjectDocument(long projectId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single Project");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ProjectEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
-
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsProjectDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ ProjectId = projectId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _projectRepository.AddProjectDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
-
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsProjectDocument newProjectDocument = new()
- {
- ProjectId = projectId,
- DocumentId = uploadResult.Document.Id,
- };
- newProjectDocument = _projectRepository.AddProjectDocument(newProjectDocument);
- _projectRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newProjectDocument);
- }
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- return relationshipResponse;
+ return;
}
- public async Task UploadLeaseDocumentAsync(long leaseId, DocumentUploadRequest uploadRequest)
+ public async Task UploadLeaseDocument(long leaseId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single Lease");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.LeaseEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
-
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsLeaseDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ LeaseId = leaseId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _leaseRepository.AddLeaseDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
-
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsLeaseDocument newDocument = new()
- {
- LeaseId = leaseId,
- DocumentId = uploadResult.Document.Id,
- };
- newDocument = _leaseRepository.AddLeaseDocument(newDocument);
- _leaseRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newDocument);
- }
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- return relationshipResponse;
+ return;
}
- public async Task UploadPropertyActivityDocumentAsync(long propertyActivityId, DocumentUploadRequest uploadRequest)
+ public async Task UploadPropertyActivityDocument(long propertyActivityId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single Property Activity");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ManagementEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
-
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsPropertyActivityDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ PimsPropertyActivityId = propertyActivityId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _propertyActivityDocumentRepository.AddPropertyActivityDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsPropertyActivityDocument newDocument = new()
- {
- PimsPropertyActivityId = propertyActivityId,
- DocumentId = uploadResult.Document.Id,
- };
- newDocument = _propertyActivityDocumentRepository.AddPropertyActivityDocument(newDocument);
- _propertyActivityDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newDocument);
- }
-
- return relationshipResponse;
+ return;
}
- public async Task UploadDispositionDocumentAsync(long dispositionFileId, DocumentUploadRequest uploadRequest)
+ public async Task UploadDispositionDocument(long dispositionFileId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single disposition file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.DispositionEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
-
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsDispositionFileDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ DispositionFileId = dispositionFileId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _dispositionFileDocumentRepository.AddDispositionDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
-
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsDispositionFileDocument newDocument = new()
- {
- DispositionFileId = dispositionFileId,
- DocumentId = uploadResult.Document.Id,
- };
- newDocument = _dispositionFileDocumentRepository.AddDispositionDocument(newDocument);
- _dispositionFileDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newDocument);
- }
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- return relationshipResponse;
+ return;
}
public async Task> DeleteResearchDocumentAsync(PimsResearchFileDocument researchFileDocument)
@@ -300,17 +228,26 @@ public async Task> DeleteResearchDocumentAsync(PimsRese
Logger.LogInformation("Deleting PIMS document for single research file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.ResearchFileEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(researchFileDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(researchFileDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(researchFileDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- researchFileDocumentRepository.DeleteResearch(researchFileDocument);
- researchFileDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+ _researchFileDocumentRepository.DeleteResearch(researchFileDocument);
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteProjectDocumentAsync(PimsProjectDocument projectDocument)
@@ -318,17 +255,26 @@ public async Task> DeleteProjectDocumentAsync(PimsProje
Logger.LogInformation("Deleting PIMS document for single Project");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.ProjectEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(projectDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(projectDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(projectDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- _projectRepository.DeleteProjectDocument(projectDocument.ProjectDocumentId);
- _projectRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+ _projectRepository.DeleteProjectDocument(projectDocument.ProjectDocumentId);
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteAcquisitionDocumentAsync(PimsAcquisitionFileDocument acquisitionFileDocument)
@@ -336,17 +282,28 @@ public async Task> DeleteAcquisitionDocumentAsync(PimsA
Logger.LogInformation("Deleting PIMS document for single acquisition file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.AcquisitionFileEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(acquisitionFileDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(acquisitionFileDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(acquisitionFileDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- acquisitionFileDocumentRepository.DeleteAcquisition(acquisitionFileDocument);
- acquisitionFileDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)acquisitionFileDocument.Document.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(acquisitionFileDocument.DocumentId);
+
+ _acquisitionFileDocumentRepository.DeleteAcquisition(acquisitionFileDocument);
+
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteLeaseDocumentAsync(PimsLeaseDocument leaseDocument)
@@ -354,17 +311,28 @@ public async Task> DeleteLeaseDocumentAsync(PimsLeaseDo
Logger.LogInformation("Deleting PIMS document for single lease");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.LeaseEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(leaseDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(leaseDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(leaseDocument.Document.DocumentId);
+
+ // 1 - Delete Mayan first.
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- _leaseRepository.DeleteLeaseDocument(leaseDocument.LeaseDocumentId);
- _leaseRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+ _leaseRepository.DeleteLeaseDocument(leaseDocument.LeaseDocumentId);
+
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeletePropertyActivityDocumentAsync(PimsPropertyActivityDocument propertyActivityDocument)
@@ -372,17 +340,27 @@ public async Task> DeletePropertyActivityDocumentAsync(
Logger.LogInformation("Deleting PIMS document for single Property Activity");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.ManagementEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(propertyActivityDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(propertyActivityDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(propertyActivityDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- _propertyActivityDocumentRepository.DeletePropertyActivityDocument(propertyActivityDocument);
- _propertyActivityDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+
+ _propertyActivityDocumentRepository.DeletePropertyActivityDocument(propertyActivityDocument);
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteDispositionDocumentAsync(PimsDispositionFileDocument dispositionFileDocument)
@@ -390,32 +368,99 @@ public async Task> DeleteDispositionDocumentAsync(PimsD
Logger.LogInformation("Deleting PIMS document for single disposition file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.DispositionEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(dispositionFileDocument.DocumentId);
- if (relationshipCount == 1)
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(dispositionFileDocument.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- return await documentService.DeleteDocumentAsync(dispositionFileDocument.Document);
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction(); // leave trace when mayan document deleted.
}
- else
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+
+ _dispositionFileDocumentRepository.DeleteDispositionDocument(dispositionFileDocument);
+
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
+ }
+
+ private PimsDocument CreatePimsDocument(DocumentUploadRequest uploadRequest, string documentExternalId = null)
+ {
+ // Create the pims document
+ PimsDocument newPimsDocument = new()
+ {
+ FileName = uploadRequest.File.FileName,
+ DocumentTypeId = uploadRequest.DocumentTypeId,
+ DocumentStatusTypeCode = uploadRequest.DocumentStatusCode,
+ MayanId = null,
+ DocumentExternalId = documentExternalId,
+ };
+
+ _documentRepository.Add(newPimsDocument);
+
+ return newPimsDocument;
+ }
+
+ private async Task GenerateQueuedDocumentItem(long documentId, DocumentUploadRequest uploadRequest)
+ {
+ PimsDocumentQueue queueDocument = new()
{
- _dispositionFileDocumentRepository.DeleteDispositionDocument(dispositionFileDocument);
- _dispositionFileDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ DocumentId = documentId,
+ Document = await uploadRequest.File.GetBytes(),
+ DocumentMetadata = uploadRequest.DocumentMetadata != null ? JsonSerializer.Serialize(uploadRequest.DocumentMetadata) : null,
+ };
+ _documentQueueRepository.Add(queueDocument);
+ }
+
+ private async Task> DeleteMayanDocument(long mayanDocumentId)
+ {
+ var result = await _documentService.DeleteMayanStorageDocumentAsync(mayanDocumentId);
+ if (result.HttpStatusCode == HttpStatusCode.NotFound)
+ {
+ result.Status = ExternalResponseStatus.Success;
}
+
+ return result;
}
- private static void ValidateZeroLengthFile(DocumentUploadRequest uploadRequest)
+ private PimsDocument RemoveDocumentMayanID(PimsDocument doc)
{
- if (uploadRequest.File is not null && uploadRequest.File.Length == 0)
+ doc.MayanId = null;
+ return _documentRepository.Update(doc, false);
+ }
+
+ private void DeleteQueuedDocumentItem(long documentId)
+ {
+ var documentQueuedItem = _documentQueueRepository.GetByDocumentId(documentId);
+ if (documentQueuedItem.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.PENDING.ToString()
+ || documentQueuedItem.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.PROCESSING.ToString())
+ {
+ throw new BadRequestException("Doucment in process can not be deleted");
+ }
+
+ bool deleted = _documentQueueRepository.Delete(documentQueuedItem);
+ if(!deleted)
{
- throw new BadRequestException("The submitted file is empty");
+ Logger.LogWarning("Failed to delete Queued Document {documentId}", documentId);
+ throw new InvalidOperationException("Could not delete document queue item");
}
}
- private static void ValidateDocumentUploadResponse(DocumentUploadResponse uploadResult)
+ private void DeleteDocument(PimsDocument document)
{
- if (uploadResult.Document is null)
+ bool deleted = _documentRepository.DeleteDocument(document);
+ if (!deleted)
{
- throw new BadRequestException("Unexpected exception uploading file", new System.Exception(uploadResult.DocumentExternalResponse.Message));
+ Logger.LogWarning("Failed to delete Document {documentId}", document.DocumentId);
+ throw new InvalidOperationException("Could not delete document");
}
}
}
diff --git a/source/backend/api/Services/DocumentQueueService.cs b/source/backend/api/Services/DocumentQueueService.cs
index 4ffb2c4363..031418e489 100644
--- a/source/backend/api/Services/DocumentQueueService.cs
+++ b/source/backend/api/Services/DocumentQueueService.cs
@@ -1,7 +1,19 @@
+using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Security.Claims;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Mayan.Document;
+using Pims.Api.Models.Requests.Document.Upload;
+using Pims.Api.Models.Requests.Http;
+using Pims.Core.Api.Exceptions;
using Pims.Core.Api.Services;
using Pims.Core.Extensions;
using Pims.Core.Http.Configuration;
@@ -17,26 +29,325 @@ namespace Pims.Api.Services
///
public class DocumentQueueService : BaseService, IDocumentQueueService
{
- private readonly IDocumentQueueRepository documentQueueRepository;
- private readonly IOptionsMonitor keycloakOptions;
+ private readonly IDocumentQueueRepository _documentQueueRepository;
+ private readonly IDocumentRepository _documentRepository;
+ private readonly IDocumentTypeRepository _documentTypeRepository;
+ private readonly IDocumentService _documentService;
+ private readonly IOptionsMonitor _keycloakOptions;
public DocumentQueueService(
ClaimsPrincipal user,
ILogger logger,
IDocumentQueueRepository documentQueueRepository,
+ IDocumentRepository documentRepository,
+ IDocumentTypeRepository documentTypeRepository,
+ IDocumentService documentService,
IOptionsMonitor options)
: base(user, logger)
{
- this.documentQueueRepository = documentQueueRepository;
- this.keycloakOptions = options;
+ this._documentQueueRepository = documentQueueRepository;
+ this._documentRepository = documentRepository;
+ this._documentTypeRepository = documentTypeRepository;
+ this._documentService = documentService;
+ this._keycloakOptions = options;
}
+ ///
+ /// Get document in the document queue based on the specified id.
+ ///
+ /// The id of the document in the queue.
+ /// that match the id criteria.
+ /// Thrown when the user is not authorized to perform this operation.
+ /// If the requested Id does not exist.
+ public PimsDocumentQueue GetById(long documentQueueId)
+ {
+ this.Logger.LogInformation("Retrieving queued PIMS document using id {documentQueueId}", documentQueueId);
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ var documentQueue = _documentQueueRepository.TryGetById(documentQueueId);
+ if (documentQueue == null)
+ {
+ throw new KeyNotFoundException($"Unable to find queued document by id: ${documentQueueId}");
+ }
+
+ return documentQueue;
+ }
+
+ ///
+ /// Searches for documents in the document queue based on the specified filter.
+ ///
+ /// The filter criteria to apply when searching the document queue.
+ /// An enumerable collection of that match the filter criteria.
+ /// Thrown when the user is not authorized to perform this operation.
public IEnumerable SearchDocumentQueue(DocumentQueueFilter filter)
{
- this.Logger.LogInformation("Retrieving queued PIMS documents using filter {filter}", filter);
- this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this.keycloakOptions);
+ this.Logger.LogInformation("Retrieving queued PIMS documents using filter {filter}", filter.Serialize());
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ var queuedDocuments = _documentQueueRepository.GetAllByFilter(filter);
+
+ if (filter.MaxFileSize != null)
+ {
+ List documentsBelowMaxFileSize = new List();
+ long totalFileSize = 0;
+ var documentIdSizeDict = _documentQueueRepository.GetFileLengthsById(queuedDocuments.Select(qd => qd.DocumentQueueId));
+ queuedDocuments.ForEach(currentDocument =>
+ {
+ long currentFileSize = documentIdSizeDict.GetValueOrDefault(currentDocument.DocumentQueueId, 0);
+ if (currentFileSize + totalFileSize <= filter.MaxFileSize)
+ {
+ totalFileSize += currentFileSize;
+ documentsBelowMaxFileSize.Add(currentDocument);
+ }
+ });
+ if(documentsBelowMaxFileSize.Count == 0 && queuedDocuments.Any())
+ {
+ documentsBelowMaxFileSize.Add(queuedDocuments.FirstOrDefault());
+ }
+ this.Logger.LogDebug("returning {length} documents below file size", documentsBelowMaxFileSize.Count);
+ return documentsBelowMaxFileSize;
+ }
+ return queuedDocuments;
+ }
+
+ ///
+ /// Updates the specified document queue.
+ ///
+ /// The document queue object to update.
+ /// The updated document queue object.
+ /// Thrown when the user is not authorized to perform this operation.
+ public PimsDocumentQueue Update(PimsDocumentQueue documentQueue)
+ {
+ this.Logger.LogInformation("Updating queued document {documentQueueId}", documentQueue.DocumentQueueId);
+ this.Logger.LogDebug("Incoming queued document {document}", documentQueue.Serialize());
+
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ _documentQueueRepository.Update(documentQueue);
+ _documentQueueRepository.CommitTransaction();
+ return documentQueue;
+ }
+
+ ///
+ /// Polls for the status of a document in mayan, and updates the queue based on the result.
+ ///
+ /// The document queue object containing the document details.
+ /// A task that represents the asynchronous operation. The task result contains the updated document queue object, or null if the polling failed.
+ /// Thrown when the user is not authorized to perform this operation.
+ /// Thrown when the document queue does not have a valid document ID or related document.
+ public async Task PollForDocument(PimsDocumentQueue documentQueue)
+ {
+ this.Logger.LogInformation("Polling queued document {documentQueueId}", documentQueue.DocumentQueueId);
+ this.Logger.LogDebug("Polling queued document {document}", documentQueue.Serialize());
+
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+ if (documentQueue.DocumentId == null)
+ {
+ this.Logger.LogError("polled queued document does not have a document Id {documentQueueId}", documentQueue.DocumentQueueId);
+ throw new InvalidDataException("DocumentId is required to poll for a document.");
+ }
+
+ var databaseDocumentQueue = _documentQueueRepository.TryGetById(documentQueue.DocumentQueueId);
+ if (databaseDocumentQueue == null)
+ {
+ this.Logger.LogError("Unable to find document queue with {id}", documentQueue.DocumentQueueId);
+ throw new KeyNotFoundException($"Unable to find document queue with matching id: {documentQueue.DocumentQueueId}");
+ }
+
+ var relatedDocument = _documentRepository.TryGet(documentQueue.DocumentId.Value);
+
+ if (relatedDocument?.MayanId == null || relatedDocument?.MayanId < 0)
+ {
+ this.Logger.LogError("Queued Document {documentQueueId} has no mayan id and is invalid.", documentQueue.DocumentQueueId);
+ databaseDocumentQueue.MayanError = "Document does not have a valid MayanId.";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+
+ ExternalResponse documentDetailsResponse = await _documentService.GetStorageDocumentDetail(relatedDocument.MayanId.Value);
+
+ if (documentDetailsResponse.Status != ExternalResponseStatus.Success || documentDetailsResponse?.Payload == null)
+ {
+ this.Logger.LogError("Polling for queued document {documentQueueId} failed with status {documentDetailsResponseStatus}", documentQueue.DocumentQueueId, documentDetailsResponse.Status);
+ databaseDocumentQueue.MayanError = "Document Polling failed.";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+
+ if (documentDetailsResponse.Payload.FileLatest?.Id == null)
+ {
+ this.Logger.LogInformation("Polling for queued document {documentQueueId} complete, file still processing", documentQueue.DocumentQueueId);
+ }
+ else
+ {
+ this.Logger.LogInformation("Polling for queued document {documentQueueId} complete, file uploaded successfully", documentQueue.DocumentQueueId);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.SUCCESS);
+ }
- return documentQueueRepository.GetAllByFilter(filter);
+ return databaseDocumentQueue;
+ }
+
+ ///
+ /// Uploads the specified document queue.
+ ///
+ /// The document queue object containing the document to upload.
+ /// A task that represents the asynchronous operation. The task result contains the updated document queue object, or null if the upload failed.
+ /// Thrown when the user is not authorized to perform this operation.
+ /// Thrown when the document queue does not have a valid document ID or related document.
+ public async Task Upload(PimsDocumentQueue documentQueue)
+ {
+ this.Logger.LogInformation("Uploading queued document {documentQueueId}", documentQueue.DocumentQueueId);
+ this.Logger.LogDebug("Uploading queued document {document}", documentQueue.Serialize());
+
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ var databaseDocumentQueue = _documentQueueRepository.TryGetById(documentQueue.DocumentQueueId);
+ if (databaseDocumentQueue == null)
+ {
+ this.Logger.LogError("Unable to find document queue with {id}", documentQueue.DocumentQueueId);
+ throw new KeyNotFoundException($"Unable to find document queue with matching id: {documentQueue.DocumentQueueId}");
+ }
+ databaseDocumentQueue.DocProcessStartDt = DateTime.UtcNow;
+
+ // if the document queued for upload is already in an error state, update the retries.
+ if (databaseDocumentQueue.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.PIMS_ERROR.ToString() || databaseDocumentQueue.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.MAYAN_ERROR.ToString())
+ {
+ this.Logger.LogDebug("Document Queue {documentQueueId}, previously errored, retrying", documentQueue.DocumentQueueId);
+ databaseDocumentQueue.DocProcessRetries += 1;
+ databaseDocumentQueue.DocProcessEndDt = null;
+ }
+
+ bool isValid = ValidateQueuedDocument(databaseDocumentQueue, documentQueue);
+ if (!isValid)
+ {
+ this.Logger.LogDebug("Document Queue {documentQueueId}, invalid, aborting upload.", documentQueue.DocumentQueueId);
+ databaseDocumentQueue.MayanError = "Document is invalid.";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+
+ PimsDocument relatedDocument = null;
+ relatedDocument = _documentRepository.TryGetDocumentRelationships(databaseDocumentQueue.DocumentId.Value);
+ if (relatedDocument?.DocumentTypeId == null)
+ {
+ databaseDocumentQueue.MayanError = "Document does not have a valid DocumentType.";
+ this.Logger.LogError("Queued document {documentQueueId} does not have a related PIMS_DOCUMENT {documentId} with valid DocumentType, aborting.", databaseDocumentQueue.DocumentQueueId, relatedDocument?.DocumentId);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+ else if (relatedDocument?.MayanId != null && relatedDocument?.MayanId > 0)
+ {
+ this.Logger.LogInformation("Queued document {documentQueueId} already has a mayan id {mayanid}, no further processing required.", databaseDocumentQueue.DocumentQueueId, relatedDocument.MayanId);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.SUCCESS);
+ return databaseDocumentQueue; // The document poll job should pick this up and fix the document queue status.
+ }
+
+ try
+ {
+ PimsDocumentTyp documentTyp = _documentTypeRepository.GetById(relatedDocument.DocumentTypeId); // throws KeyNotFoundException if not found.
+
+ IFormFile file = null;
+ using MemoryStream memStream = new(databaseDocumentQueue.Document);
+ file = new FormFile(memStream, 0, databaseDocumentQueue.Document.Length, relatedDocument.FileName, relatedDocument.FileName);
+
+ DocumentUploadRequest request = new DocumentUploadRequest()
+ {
+ File = file,
+ DocumentStatusCode = relatedDocument.DocumentStatusTypeCode,
+ DocumentTypeId = relatedDocument.DocumentTypeId,
+ DocumentTypeMayanId = documentTyp.MayanId,
+ DocumentId = relatedDocument.DocumentId,
+ DocumentMetadata = databaseDocumentQueue.DocumentMetadata != null ? JsonSerializer.Deserialize>(databaseDocumentQueue.DocumentMetadata) : null,
+ };
+ this.Logger.LogDebug("Document Queue {documentQueueId}, beginning upload.", documentQueue.DocumentQueueId);
+ DocumentUploadResponse response = await _documentService.UploadDocumentAsync(request, true);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PROCESSING); // Set the status to processing, as the document is now being uploaded. Must be set after the mayan id is set, so that the poll logic functions correctly.
+
+ if (response.DocumentExternalResponse.Status != ExternalResponseStatus.Success || response?.DocumentExternalResponse?.Payload == null)
+ {
+ this.Logger.LogError(
+ "Queued document upload failed {databaseDocumentQueueDocumentQueueId} {databaseDocumentQueueDocumentQueueStatusTypeCode}, {documentExternalResponseStatus}",
+ databaseDocumentQueue.DocumentQueueId,
+ databaseDocumentQueue.DocumentQueueStatusTypeCode,
+ response.DocumentExternalResponse.Status);
+
+ databaseDocumentQueue.MayanError = $"Failed to upload document, mayan error: {response.DocumentExternalResponse.Message}";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.MAYAN_ERROR);
+ return databaseDocumentQueue;
+ }
+ response.MetadataExternalResponse.Where(r => r.Status != ExternalResponseStatus.Success).ForEach(r => this.Logger.LogError("url: ${url} status: ${status} message ${message}", r.Payload.Url, r.Status, r.Message)); // Log any metadata errors, but don't fail the upload.
+
+ // Mayan may have already returned a file id from the original upload. If not, this job will remain in the processing state (to be periodically checked for completion in another job).
+ if (response.DocumentExternalResponse?.Payload?.FileLatest?.Id != null)
+ {
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.SUCCESS);
+ }
+ }
+ catch (Exception ex) when (ex is BadRequestException || ex is KeyNotFoundException || ex is InvalidDataException || ex is JsonException)
+ {
+ this.Logger.LogError($"Error: {ex.Message}");
+ databaseDocumentQueue.MayanError = ex.Message;
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ }
+ return databaseDocumentQueue;
+ }
+
+ ///
+ /// Updates the status of the specified document queue.
+ ///
+ /// The document queue object to update.
+ /// The new status type to set for the document queue.
+ ///
+ /// This method updates the document queue's status and commits the transaction.
+ /// If the status is a final state, it also updates the processing end date.
+ ///
+ private void UpdateDocumentQueueStatus(PimsDocumentQueue documentQueue, DocumentQueueStatusTypes statusType)
+ {
+ documentQueue.DocumentQueueStatusTypeCode = statusType.ToString();
+ bool removeDocument = false;
+
+ // Any final states should update the processing end date.
+ if (statusType != DocumentQueueStatusTypes.PROCESSING && statusType != DocumentQueueStatusTypes.PENDING)
+ {
+ documentQueue.DocProcessEndDt = DateTime.UtcNow;
+ if (statusType == DocumentQueueStatusTypes.SUCCESS)
+ {
+ documentQueue.Document = null;
+ removeDocument = true;
+ }
+ }
+ _documentQueueRepository.Update(documentQueue, removeDocument);
+ _documentQueueRepository.CommitTransaction();
+ }
+
+ ///
+ /// Validates the queued document against the database document queue.
+ ///
+ /// The document queue object from the database.
+ /// The document queue object to validate against the database.
+ /// True if the queued document is valid; otherwise, false.
+ ///
+ /// This method checks if the status type, process retries, and document content are valid.
+ /// It also ensures that at least one file document ID is associated with the document.
+ ///
+ private bool ValidateQueuedDocument(PimsDocumentQueue databaseDocumentQueue, PimsDocumentQueue externalDocument)
+ {
+ if (databaseDocumentQueue.DocumentQueueStatusTypeCode != externalDocument.DocumentQueueStatusTypeCode)
+ {
+ this.Logger.LogError("Requested document queue status: {documentQueueStatusTypeCode} does not match current database status: {documentQueueStatusTypeCode}", externalDocument.DocumentQueueStatusTypeCode, databaseDocumentQueue.DocumentQueueStatusTypeCode);
+ return false;
+ }
+ else if (databaseDocumentQueue.DocProcessRetries != externalDocument.DocProcessRetries)
+ {
+ this.Logger.LogError("Requested document retries: {documentQueueStatusTypeCode} does not match current database retries: {documentQueueStatusTypeCode}", externalDocument.DocumentQueueStatusTypeCode, databaseDocumentQueue.DocumentQueueStatusTypeCode);
+ return false;
+ }
+ else if (databaseDocumentQueue.Document == null || databaseDocumentQueue.DocumentId == null)
+ {
+ this.Logger.LogError("Queued document file content is empty, unable to upload.");
+ return false;
+ }
+ return true;
}
}
}
diff --git a/source/backend/api/Services/DocumentService.cs b/source/backend/api/Services/DocumentService.cs
index 333fdb1c2b..2407d796ed 100644
--- a/source/backend/api/Services/DocumentService.cs
+++ b/source/backend/api/Services/DocumentService.cs
@@ -83,7 +83,8 @@ public DocumentService(
IDocumentTypeRepository documentTypeRepository,
IAvService avService,
IMapper mapper,
- IOptionsMonitor options)
+ IOptionsMonitor options,
+ IDocumentQueueRepository queueRepository)
: base(user, logger)
{
this.documentRepository = documentRepository;
@@ -136,9 +137,9 @@ public IList GetPimsDocumentTypes(DocumentRelationType relation
return documentTypeRepository.GetByCategory(categoryType);
}
- public async Task UploadDocumentAsync(DocumentUploadRequest uploadRequest)
+ public async Task UploadDocumentSync(DocumentUploadRequest uploadRequest)
{
- this.Logger.LogInformation("Uploading document");
+ this.Logger.LogInformation("Uploading document and waiting for mayan upload.");
this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
ExternalResponse externalResponse = await UploadDocumentAsync(uploadRequest.DocumentTypeMayanId, uploadRequest.File);
@@ -173,6 +174,7 @@ public async Task UploadDocumentAsync(DocumentUploadRequ
{
_ = PrecacheDocumentPreviews(externalDocument.Id, externalDocument.FileLatest.Id);
}
+
// Create metadata of document
if (uploadRequest.DocumentMetadata != null)
{
@@ -209,6 +211,63 @@ public async Task UploadDocumentAsync(DocumentUploadRequ
return response;
}
+ public async Task UploadDocumentAsync(DocumentUploadRequest uploadRequest, bool skipExtensionCheck = false)
+ {
+ this.Logger.LogInformation("Uploading document, do not wait for mayan processing. documentId: {documentId}", uploadRequest.DocumentId);
+ this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
+
+ ExternalResponse externalResponse = await UploadDocumentAsync(uploadRequest.DocumentTypeMayanId, uploadRequest.File, skipExtensionCheck);
+ DocumentUploadResponse response = new DocumentUploadResponse()
+ {
+ DocumentExternalResponse = externalResponse,
+ MetadataExternalResponse = new List>(),
+ };
+
+ PimsDocument databaseDocument = documentRepository.TryGet(uploadRequest.DocumentId);
+ response.Document = databaseDocument != null ? mapper.Map(databaseDocument) : null;
+
+ if (response?.DocumentExternalResponse?.Payload?.Id != null && response?.DocumentExternalResponse?.Payload?.Id > 0 && databaseDocument != null)
+ {
+ // Create metadata of document
+ if (uploadRequest.DocumentMetadata != null)
+ {
+ List creates = new List();
+ foreach (var metadata in uploadRequest.DocumentMetadata)
+ {
+ if (!string.IsNullOrEmpty(metadata.Value))
+ {
+ creates.Add(metadata);
+ }
+ }
+
+ response.MetadataExternalResponse = await CreateMetadata(response.DocumentExternalResponse.Payload.Id, creates);
+ }
+
+ databaseDocument.MayanId = response.DocumentExternalResponse.Payload.Id;
+ documentRepository.Update(databaseDocument);
+ documentRepository.CommitTransaction();
+ }
+ else
+ {
+ this.Logger.LogError("Failed to update associated PIMS document with uploaded Mayan Id. documentId: {documentId}", uploadRequest.DocumentId);
+ this.Logger.LogDebug("Mayan response: {response}", response.Serialize());
+ }
+
+ return response;
+ }
+
+ public PimsDocument AddDocument(PimsDocument newPimsDocument)
+ {
+ this.Logger.LogInformation("Adding document uploaded asynchronously.");
+ this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
+ newPimsDocument.ThrowIfNull(nameof(newPimsDocument));
+
+ documentRepository.Add(newPimsDocument);
+ documentRepository.CommitTransaction();
+
+ return newPimsDocument;
+ }
+
public async Task UpdateDocumentAsync(DocumentUpdateRequest updateRequest)
{
this.Logger.LogInformation("Updating document {documentId}", updateRequest.DocumentId);
@@ -314,8 +373,8 @@ public async Task UpdateDocumentAsync(DocumentUpdateRequ
public async Task> DeleteDocumentAsync(PimsDocument document)
{
- this.Logger.LogInformation("Deleting document {documentId}", document.Internal_Id);
- this.User.ThrowIfNotAuthorized(Permissions.DocumentDelete);
+ Logger.LogInformation("Deleting document {documentId}", document.Internal_Id);
+ User.ThrowIfNotAuthorized(Permissions.DocumentDelete);
var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
if (document.MayanId.HasValue)
@@ -337,6 +396,26 @@ public async Task> DeleteDocumentAsync(PimsDocument doc
{
throw GetMayanResponseError(result.Message);
}
+
+ return result;
+ }
+
+ public async Task> DeleteMayanStorageDocumentAsync(long mayanDocumentId)
+ {
+ Logger.LogInformation("Deleting Mayan document {documentId}", mayanDocumentId);
+ User.ThrowIfNotAuthorized(Permissions.DocumentDelete);
+
+ ExternalResponse result = await documentStorageRepository.TryDeleteDocument(mayanDocumentId);
+ if(result.Status == ExternalResponseStatus.Error && result.HttpStatusCode == HttpStatusCode.NotFound)
+ {
+ return result;
+ }
+
+ if (result.Status == ExternalResponseStatus.Error || result.Status == ExternalResponseStatus.NotExecuted)
+ {
+ throw GetMayanResponseError(result.Message);
+ }
+
return result;
}
@@ -591,13 +670,13 @@ private async Task PrecacheDocumentPreviews(long documentId, long documentFileId
}
}
- private async Task> UploadDocumentAsync(long documentType, IFormFile fileRaw)
+ private async Task> UploadDocumentAsync(long documentType, IFormFile fileRaw, bool skipExtensionCheck = false)
{
this.Logger.LogInformation("Uploading storage document {documentType}", documentType);
this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
await this.avService.ScanAsync(fileRaw);
- if (IsValidDocumentExtension(fileRaw.FileName))
+ if (skipExtensionCheck || IsValidDocumentExtension(fileRaw.FileName))
{
ExternalResponse result = await documentStorageRepository.TryUploadDocumentAsync(documentType, fileRaw);
return result;
diff --git a/source/backend/api/Services/FormDocumentService.cs b/source/backend/api/Services/FormDocumentService.cs
index 00cab505bd..b95ece2431 100644
--- a/source/backend/api/Services/FormDocumentService.cs
+++ b/source/backend/api/Services/FormDocumentService.cs
@@ -80,14 +80,14 @@ public async Task UploadFormDocumentTemplate
}
}
- DocumentUploadResponse uploadResult = await _documentService.UploadDocumentAsync(uploadRequest);
+ DocumentUploadResponse uploadResult = await _documentService.UploadDocumentSync(uploadRequest);
DocumentUploadRelationshipResponse relationshipResponse = new DocumentUploadRelationshipResponse()
{
UploadResponse = uploadResult,
};
- if (uploadResult.DocumentExternalResponse.Status == ExternalResponseStatus.Success && uploadResult.Document != null && uploadResult.Document.Id != 0)
+ if (uploadResult.DocumentExternalResponse.Status == ExternalResponseStatus.Success)
{
currentFormType.DocumentId = uploadResult.Document.Id;
var updatedFormType = _formTypeRepository.SetFormTypeDocument(currentFormType);
@@ -138,7 +138,7 @@ public PimsAcquisitionFileForm AddAcquisitionForm(PimsFormType formType, long ac
public IEnumerable GetAcquisitionForms(long acquisitionFileId)
{
- _logger.LogInformation("Getting acquisition forms by acquisition file id ...", acquisitionFileId);
+ _logger.LogInformation("Getting acquisition forms by acquisition file id {acquisitionFileId}", acquisitionFileId);
this.User.ThrowIfNotAuthorized(Permissions.FormView, Permissions.AcquisitionFileView);
var fileForms = _acquisitionFileFormRepository.GetAllByAcquisitionFileId(acquisitionFileId);
@@ -147,7 +147,7 @@ public IEnumerable GetAcquisitionForms(long acquisition
public PimsAcquisitionFileForm GetAcquisitionForm(long fileFormId)
{
- _logger.LogInformation("Getting acquisition form by form file id ...", fileFormId);
+ _logger.LogInformation("Getting acquisition form by form file id {fileFormId}", fileFormId);
this.User.ThrowIfNotAuthorized(Permissions.FormView, Permissions.AcquisitionFileView);
var fileForm = _acquisitionFileFormRepository.GetByAcquisitionFileFormId(fileFormId);
@@ -156,7 +156,7 @@ public PimsAcquisitionFileForm GetAcquisitionForm(long fileFormId)
public bool DeleteAcquisitionFileForm(long fileFormId)
{
- _logger.LogInformation("Deleting acquisition file form id ...", fileFormId);
+ _logger.LogInformation("Deleting acquisition file form id {fileFormId}", fileFormId);
this.User.ThrowIfNotAuthorized(Permissions.FormDelete, Permissions.AcquisitionFileEdit);
var fileFormToDelete = _acquisitionFileFormRepository.TryDelete(fileFormId);
diff --git a/source/backend/api/Services/IDocumentFileService.cs b/source/backend/api/Services/IDocumentFileService.cs
index 987f34bcc2..aa56e119fb 100644
--- a/source/backend/api/Services/IDocumentFileService.cs
+++ b/source/backend/api/Services/IDocumentFileService.cs
@@ -15,17 +15,17 @@ public interface IDocumentFileService
public IList GetFileDocuments(FileType fileType, long fileId)
where T : PimsFileDocument;
- Task UploadResearchDocumentAsync(long researchFileId, DocumentUploadRequest uploadRequest);
+ Task UploadAcquisitionDocument(long acquisitionFileId, DocumentUploadRequest uploadRequest);
- Task UploadAcquisitionDocumentAsync(long acquisitionFileId, DocumentUploadRequest uploadRequest);
+ Task UploadResearchDocument(long researchFileId, DocumentUploadRequest uploadRequest);
- Task UploadLeaseDocumentAsync(long leaseId, DocumentUploadRequest uploadRequest);
+ Task UploadProjectDocument(long projectId, DocumentUploadRequest uploadRequest);
- Task UploadProjectDocumentAsync(long projectId, DocumentUploadRequest uploadRequest);
+ Task UploadLeaseDocument(long leaseId, DocumentUploadRequest uploadRequest);
- Task UploadPropertyActivityDocumentAsync(long propertyActivityId, DocumentUploadRequest uploadRequest);
+ Task UploadPropertyActivityDocument(long propertyActivityId, DocumentUploadRequest uploadRequest);
- Task UploadDispositionDocumentAsync(long dispositionFileId, DocumentUploadRequest uploadRequest);
+ Task UploadDispositionDocument(long dispositionFileId, DocumentUploadRequest uploadRequest);
Task> DeleteResearchDocumentAsync(PimsResearchFileDocument researchFileDocument);
diff --git a/source/backend/api/Services/IDocumentQueueService.cs b/source/backend/api/Services/IDocumentQueueService.cs
index b1f3c09206..2a797d7df8 100644
--- a/source/backend/api/Services/IDocumentQueueService.cs
+++ b/source/backend/api/Services/IDocumentQueueService.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
@@ -9,6 +10,14 @@ namespace Pims.Api.Services
///
public interface IDocumentQueueService
{
+ public PimsDocumentQueue GetById(long documentQueueId);
+
public IEnumerable SearchDocumentQueue(DocumentQueueFilter filter);
+
+ public PimsDocumentQueue Update(PimsDocumentQueue documentQueue);
+
+ public Task PollForDocument(PimsDocumentQueue documentQueue);
+
+ public Task Upload(PimsDocumentQueue documentQueue);
}
}
diff --git a/source/backend/api/Services/IDocumentService.cs b/source/backend/api/Services/IDocumentService.cs
index ebcd1ded2e..c37d3b6552 100644
--- a/source/backend/api/Services/IDocumentService.cs
+++ b/source/backend/api/Services/IDocumentService.cs
@@ -38,10 +38,14 @@ public interface IDocumentService
IList GetPimsDocumentTypes(DocumentRelationType relationshipType);
- Task UploadDocumentAsync(DocumentUploadRequest uploadRequest);
+ Task UploadDocumentAsync(DocumentUploadRequest uploadRequest, bool skipExtensionCheck = false);
+
+ Task UploadDocumentSync(DocumentUploadRequest uploadRequest);
Task UpdateDocumentAsync(DocumentUpdateRequest updateRequest);
+ Task> DeleteMayanStorageDocumentAsync(long mayanDocumentId);
+
Task> DeleteDocumentAsync(PimsDocument document);
Task> GetStorageDocumentDetail(long mayanDocumentId);
@@ -49,5 +53,7 @@ public interface IDocumentService
Task>> GetDocumentFilePageListAsync(long documentId, long documentFileId);
Task DownloadFilePageImageAsync(long mayanDocumentId, long mayanFileId, long mayanFilePageId);
+
+ PimsDocument AddDocument(PimsDocument newPimsDocument);
}
}
diff --git a/source/backend/api/Services/ResearchFileService.cs b/source/backend/api/Services/ResearchFileService.cs
index f28025f757..81b66e5e67 100644
--- a/source/backend/api/Services/ResearchFileService.cs
+++ b/source/backend/api/Services/ResearchFileService.cs
@@ -5,12 +5,11 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pims.Core.Extensions;
+using Pims.Core.Security;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
using Pims.Dal.Exceptions;
-using Pims.Dal.Helpers.Extensions;
using Pims.Dal.Repositories;
-using Pims.Core.Security;
namespace Pims.Api.Services
{
@@ -120,6 +119,7 @@ public PimsResearchFile UpdateProperties(PimsResearchFile researchFile, IEnumera
{
incomingResearchProperty.Internal_Id = matchingProperty.Internal_Id;
}
+
// If the property is not new, check if the name has been updated.
if (incomingResearchProperty.Internal_Id != 0)
{
@@ -173,7 +173,7 @@ public Paged GetPage(ResearchFilter filter)
{
_logger.LogInformation("Searching for research files...");
- _logger.LogDebug("Research file search with filter", filter);
+ _logger.LogDebug("Research file search with filter {filter}", filter.Serialize());
_user.ThrowIfNotAuthorized(Permissions.ResearchFileView);
return _researchFileRepository.GetPage(filter);
diff --git a/source/backend/api/Startup.cs b/source/backend/api/Startup.cs
index ba3ab2fb7f..d0e9c5b8c2 100644
--- a/source/backend/api/Startup.cs
+++ b/source/backend/api/Startup.cs
@@ -31,7 +31,6 @@
using Microsoft.OpenApi.Models;
using Pims.Api.Handlers;
using Pims.Api.Helpers;
-using Pims.Core.Api.Exceptions;
using Pims.Api.Helpers.Healthchecks;
using Pims.Api.Helpers.HealthChecks;
using Pims.Api.Helpers.Mapping;
@@ -42,21 +41,23 @@
using Pims.Api.Services;
using Pims.Api.Services.Interfaces;
using Pims.Av;
+using Pims.Core.Api.Exceptions;
using Pims.Core.Api.Helpers;
+using Pims.Core.Api.Middleware;
using Pims.Core.Converters;
using Pims.Core.Http;
using Pims.Core.Json;
using Pims.Dal;
using Pims.Dal.Keycloak;
+using Pims.Dal.Repositories;
using Pims.Geocoder;
using Pims.Ltsa;
using Prometheus;
-using Pims.Core.Api.Middleware;
namespace Pims.Api
{
///
- /// Startup class, provides a way to startup the .netcore RESTful API and configure it.
+ /// Startup class, provides a way to startup the .netcore REST API and configure it.
///
[ExcludeFromCodeCoverage]
public class Startup
@@ -482,6 +483,7 @@ private static void AddPimsApiRepositories(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddSingleton();
}
@@ -524,6 +526,7 @@ private static void AddPimsApiServices(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
}
///
diff --git a/source/backend/api/appsettings.Development.json b/source/backend/api/appsettings.Development.json
index 987052db9c..7df068180a 100644
--- a/source/backend/api/appsettings.Development.json
+++ b/source/backend/api/appsettings.Development.json
@@ -31,7 +31,7 @@
}
},
"ConnectionStrings": {
- "PIMS": "Server=localhost,5433;uid=admin;Database=pims;Password=Password12"
+ "PIMS": "Server=localhost,5433;User ID=admin;Database=pims;TrustServerCertificate=True;Encrypt=false;"
},
"Pims": {
"Environment": {
diff --git a/source/backend/apimodels/CodeTypes/DataSourceTypes.cs b/source/backend/apimodels/CodeTypes/DataSourceTypes.cs
new file mode 100644
index 0000000000..d608790cae
--- /dev/null
+++ b/source/backend/apimodels/CodeTypes/DataSourceTypes.cs
@@ -0,0 +1,66 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.CodeTypes
+{
+ [JsonConverter(typeof(JsonStringEnumMemberConverter))]
+ public enum DataSourceTypes
+ {
+ [EnumMember(Value = "BIP")]
+ BIP,
+
+ [EnumMember(Value = "GAZ")]
+ GAZ,
+
+ [EnumMember(Value = "GWP")]
+ GWP,
+
+ [EnumMember(Value = "LIS")]
+ LIS,
+
+ [EnumMember(Value = "LIS_OPSS")]
+ LIS_OPSS,
+
+ [EnumMember(Value = "LIS_OPSS_PAIMS")]
+ LIS_OPSS_PAIMS,
+
+ [EnumMember(Value = "LIS_OPSS_PAIMS_PMBC")]
+ LIS_OPSS_PAIMS_PMBC,
+
+ [EnumMember(Value = "LIS_PAIMS")]
+ LIS_PAIMS,
+
+ [EnumMember(Value = "LIS_PAIMS_PMBC")]
+ LIS_PAIMS_PMBC,
+
+ [EnumMember(Value = "LIS_PMBC")]
+ LIS_PMBC,
+
+ [EnumMember(Value = "OPSS")]
+ OPSS,
+
+ [EnumMember(Value = "OPSS_PAIMS")]
+ OPSS_PAIMS,
+
+ [EnumMember(Value = "PAIMS")]
+ PAIMS,
+
+ [EnumMember(Value = "PAIMS_PMBC")]
+ PAIMS_PMBC,
+
+ [EnumMember(Value = "PAT")]
+ PAT,
+
+ [EnumMember(Value = "PIMS")]
+ PIMS,
+
+ [EnumMember(Value = "PMBC")]
+ PMBC,
+
+ [EnumMember(Value = "SHAREPOINT")]
+ SHAREPOINT,
+
+ [EnumMember(Value = "TAP")]
+ TAP,
+ }
+}
diff --git a/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs b/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs
index 3b38b2899d..f13b022817 100644
--- a/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs
+++ b/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs
@@ -6,7 +6,6 @@ namespace Pims.Api.Models.CodeTypes
[JsonConverter(typeof(JsonStringEnumMemberConverter))]
public enum DocumentQueueStatusTypes
{
-
[EnumMember(Value = "MAYAN_ERROR")]
MAYAN_ERROR,
diff --git a/source/backend/apimodels/CodeTypes/DocumentStatusTypes.cs b/source/backend/apimodels/CodeTypes/DocumentStatusTypes.cs
new file mode 100644
index 0000000000..95f6372c7e
--- /dev/null
+++ b/source/backend/apimodels/CodeTypes/DocumentStatusTypes.cs
@@ -0,0 +1,40 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.CodeTypes
+{
+ [JsonConverter(typeof(JsonStringEnumMemberConverter))]
+ public enum DocumentStatusTypes
+ {
+
+ [EnumMember(Value = "AMENDD")]
+ AMENDD,
+
+ [EnumMember(Value = "APPROVD")]
+ APPROVD,
+
+ [EnumMember(Value = "CNCLD")]
+ CNCLD,
+
+ [EnumMember(Value = "DRAFT")]
+ DRAFT,
+
+ [EnumMember(Value = "FINAL")]
+ FINAL,
+
+ [EnumMember(Value = "NONE")]
+ NONE,
+
+ [EnumMember(Value = "RGSTRD")]
+ RGSTRD,
+
+ [EnumMember(Value = "SENT")]
+ SENT,
+
+ [EnumMember(Value = "SIGND")]
+ SIGND,
+
+ [EnumMember(Value = "UNREGD")]
+ UNREGD,
+ }
+}
diff --git a/source/backend/apimodels/Models/Base/CodeTypeMap.cs b/source/backend/apimodels/Models/Base/CodeTypeMap.cs
index 1559535948..ed7e2b5720 100644
--- a/source/backend/apimodels/Models/Base/CodeTypeMap.cs
+++ b/source/backend/apimodels/Models/Base/CodeTypeMap.cs
@@ -13,7 +13,6 @@ public void Register(TypeAdapterConfig config)
.Map("IsDisabled", "IsDisabled")
.Map("DisplayOrder", "DisplayOrder");
-
config.ForType(typeof(Entity.ITypeEntity), typeof(CodeTypeModel))
.Map("Id", "Id")
.Map("Description", "Description")
diff --git a/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs b/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs
index 98c7252660..5fe0e656fe 100644
--- a/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs
+++ b/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Mapster;
using Pims.Api.Models.Base;
using Entity = Pims.Dal.Entities;
@@ -14,6 +15,7 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.DocumentType, src => src.DocumentType)
.Map(dest => dest.StatusTypeCode, src => src.DocumentStatusTypeCodeNavigation)
.Map(dest => dest.FileName, src => src.FileName)
+ .Map(dest => dest.DocumentQueueStatusTypeCode, src => src.PimsDocumentQueues.Count > 0 ? src.PimsDocumentQueues.FirstOrDefault().DocumentQueueStatusTypeCodeNavigation : null)
.Inherits();
config.NewConfig()
diff --git a/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs b/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs
index d46734b021..6fea721ad2 100644
--- a/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs
+++ b/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs
@@ -18,7 +18,7 @@ public class DocumentModel : BaseAuditModel
///
/// get/set - The document id on the external storage.
///
- public int MayanDocumentId { get; set; }
+ public int? MayanDocumentId { get; set; }
///
/// get/set - Document Type.
@@ -34,6 +34,11 @@ public class DocumentModel : BaseAuditModel
/// get/set - Document/File Name.
///
public string FileName { get; set; }
+
+ ///
+ /// get/set - The document queue status type.
+ ///
+ public CodeTypeModel DocumentQueueStatusTypeCode { get; set; }
#endregion
}
}
diff --git a/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs b/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs
index 5f558d1d51..46aa23053d 100644
--- a/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs
+++ b/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs
@@ -33,7 +33,7 @@ public class DocumentTypeModel : BaseAuditModel
///
/// get/set - The document type id in mayan.
///
- public long MayanId { get; set; }
+ public long? MayanId { get; set; }
///
/// get/set - The document type is disabled and is maintained for reference only.
diff --git a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs
index 1a98ba0d07..22f5018cae 100644
--- a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs
+++ b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs
@@ -1,5 +1,6 @@
using Mapster;
using Pims.Api.Models.Base;
+using Pims.Dal.Entities;
using Entity = Pims.Dal.Entities;
namespace Pims.Api.Models.Concepts.Document
@@ -12,12 +13,14 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.Id, src => src.DocumentQueueId)
.Map(dest => dest.DocumentExternalId, src => src.DocumentExternalId)
.Map(dest => dest.DocumentId, src => src.DocumentId)
- .Map(dest => dest.DocumentQueueStatusType, src => src.DocumentQueueStatusTypeCodeNavigation)
+ .Map(dest => dest.DocumentQueueStatusType, src => src.DocumentQueueStatusTypeCodeNavigation == null ? new PimsDocumentQueueStatusType() { Id = src.DocumentQueueStatusTypeCode } : src.DocumentQueueStatusTypeCodeNavigation)
.Map(dest => dest.DataSourceTypeCode, src => src.DataSourceTypeCodeNavigation)
.Map(dest => dest.DocumentProcessStartTimestamp, src => src.DocProcessStartDt)
.Map(dest => dest.DocumentProcessEndTimestamp, src => src.DocProcessEndDt)
.Map(dest => dest.DocumentProcessRetries, src => src.DocProcessRetries)
- .Map(dest => dest.Document, src => src.Document)
+ .Map(dest => dest.PimsDocument, src => src.DocumentNavigation)
+ .Map(dest => dest.MayanError, src => src.MayanError)
+ .Map(dest => dest.DocumentQueueStatusTypeCode, src => src.DocumentQueueStatusTypeCodeNavigation)
.Inherits();
config.NewConfig()
@@ -29,7 +32,9 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.DocProcessStartDt, src => src.DocumentProcessStartTimestamp)
.Map(dest => dest.DocProcessEndDt, src => src.DocumentProcessEndTimestamp)
.Map(dest => dest.DocProcessRetries, src => src.DocumentProcessRetries)
+ .Map(dest => dest.DocumentNavigation, src => src.PimsDocument)
.Map(dest => dest.Document, src => src.Document)
+ .Map(dest => dest.MayanError, src => src.MayanError)
.Inherits();
}
}
diff --git a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs
index 73d553f74d..aa7e0c27f6 100644
--- a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs
+++ b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs
@@ -57,11 +57,19 @@ public class DocumentQueueModel : BaseAuditModel
public string MayanError { get; set; }
///
- /// get/set - The actual document, represented as a byte[].
+ /// get/set - The related pims document.
///
- public byte[] Document { get; set; }
+ public DocumentModel PimsDocument { get; set; }
+ ///
+ /// get/set - The actual document content, as a byte array.
+ ///
+ public byte[] Document { get; set; }
- #endregion
- }
+ ///
+ /// get/set - The queue status type.
+ ///
+ public CodeTypeModel DocumentQueueStatusTypeCode { get; set; }
+ #endregion
+}
}
diff --git a/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs b/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs
index 83102292e3..7d08d437e5 100644
--- a/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs
+++ b/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.IO;
using Microsoft.AspNetCore.Http;
using Pims.Api.Models.Concepts.Document;
@@ -21,6 +22,11 @@ public class DocumentUploadRequest
///
public long DocumentTypeId { get; set; }
+ ///
+ /// get/set - The id of the document to be uploaded (in PIMS).
+ ///
+ public long DocumentId { get; set; }
+
///
/// get/set - Initial status code of the document.
///
diff --git a/source/backend/core.api/Pims.Core.Api.csproj b/source/backend/core.api/Pims.Core.Api.csproj
index 70c947b9d1..51d218ea46 100644
--- a/source/backend/core.api/Pims.Core.Api.csproj
+++ b/source/backend/core.api/Pims.Core.Api.csproj
@@ -20,6 +20,7 @@
+
diff --git a/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs b/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs
index 214d359f9b..d0aaa708d2 100644
--- a/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs
+++ b/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs
@@ -10,6 +10,7 @@
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Requests.Http;
@@ -23,6 +24,7 @@ public abstract class BaseRestRepository : IRestRespository
{
protected readonly IHttpClientFactory _httpClientFactory;
protected readonly ILogger _logger;
+ protected readonly IOptions _jsonOptions;
///
/// Initializes a new instance of the class.
@@ -31,10 +33,12 @@ public abstract class BaseRestRepository : IRestRespository
/// Injected Httpclient factory.
protected BaseRestRepository(
ILogger logger,
- IHttpClientFactory httpClientFactory)
+ IHttpClientFactory httpClientFactory,
+ IOptions jsonOptions)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
+ _jsonOptions = jsonOptions;
}
public abstract void AddAuthentication(HttpClient client, string authenticationToken = null);
@@ -305,7 +309,7 @@ private async Task> ProcessResponse(HttpResponseMessage r
};
_logger.LogTrace("Response: {response}", response);
- string payload = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
+ var payload = await response.Content.ReadAsStreamAsync().ConfigureAwait(true);
result.HttpStatusCode = response.StatusCode;
switch (response.StatusCode)
@@ -321,7 +325,7 @@ private async Task> ProcessResponse(HttpResponseMessage r
result.Payload = (T)Convert.ChangeType(payload, typeof(T), CultureInfo.InvariantCulture);
break;
default:
- T requestTokenResult = JsonSerializer.Deserialize(payload);
+ T requestTokenResult = JsonSerializer.Deserialize(payload, _jsonOptions.Value);
result.Payload = requestTokenResult;
break;
}
@@ -342,7 +346,7 @@ private async Task> ProcessResponse(HttpResponseMessage r
case HttpStatusCode.BadRequest:
case HttpStatusCode.MethodNotAllowed:
result.Status = ExternalResponseStatus.Error;
- result.Message = payload;
+ result.Message = await response.Content.ReadAsStringAsync();
break;
default:
result.Status = ExternalResponseStatus.Error;
diff --git a/source/backend/dal/IRepository.cs b/source/backend/dal/IRepository.cs
index 247773ffde..4188616194 100644
--- a/source/backend/dal/IRepository.cs
+++ b/source/backend/dal/IRepository.cs
@@ -1,9 +1,15 @@
+using Microsoft.EntityFrameworkCore.Storage;
+
namespace Pims.Dal
{
public interface IRepository
{
#region Methods
+ IDbContextTransaction BeginTransaction();
+
+ void SaveChanges();
+
void CommitTransaction();
#endregion
}
diff --git a/source/backend/dal/Pims.Dal.csproj b/source/backend/dal/Pims.Dal.csproj
index d1f653dc4f..453a8d2c64 100644
--- a/source/backend/dal/Pims.Dal.csproj
+++ b/source/backend/dal/Pims.Dal.csproj
@@ -36,7 +36,7 @@
-
+
diff --git a/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs b/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs
index 56b967aa60..f0636616d9 100644
--- a/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs
+++ b/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs
@@ -41,6 +41,9 @@ public IList GetAllByAcquisitionFile(long fileId)
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(ad => ad.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(ad => ad.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(ad => ad.AcquisitionFileId == fileId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/AcquisitionFileRepository.cs b/source/backend/dal/Repositories/AcquisitionFileRepository.cs
index fbac91b873..b558efca19 100644
--- a/source/backend/dal/Repositories/AcquisitionFileRepository.cs
+++ b/source/backend/dal/Repositories/AcquisitionFileRepository.cs
@@ -926,7 +926,7 @@ private IQueryable GetCommonAcquisitionFileQueryDeep(Acquis
.ThenInclude(fp => fp.AlternateProject)
.Where(predicate);
- query = (filter.Sort?.Any() == true) ? query.OrderByProperty(true, filter.Sort) : query.OrderBy(acq => acq.AcquisitionFileId);
+ query = (filter.Sort?.Length > 0) ? query.OrderByProperty(true, filter.Sort) : query.OrderBy(acq => acq.AcquisitionFileId);
return query;
}
diff --git a/source/backend/dal/Repositories/BaseRepository.cs b/source/backend/dal/Repositories/BaseRepository.cs
index 1261a87011..f8486b76a6 100644
--- a/source/backend/dal/Repositories/BaseRepository.cs
+++ b/source/backend/dal/Repositories/BaseRepository.cs
@@ -1,4 +1,5 @@
using System.Security.Claims;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
namespace Pims.Dal.Repositories
@@ -44,6 +45,23 @@ protected BaseRepository(PimsContext dbContext, ClaimsPrincipal user, ILogger
+ /// Begin a DB transaction.
+ ///
+ ///
+ public IDbContextTransaction BeginTransaction()
+ {
+ return this.Context.Database.BeginTransaction();
+ }
+
+ ///
+ /// Save changes for a DB action.
+ ///
+ public void SaveChanges()
+ {
+ Context.SaveChanges();
+ }
+
///
/// Commit all saved changes as a single transaction.
///
diff --git a/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs b/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs
index 2781d0d459..6173347ee4 100644
--- a/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs
+++ b/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs
@@ -41,11 +41,6 @@ public T_Entity Find(params object[] keyValues)
{
return this.Context.Find(keyValues);
}
-
- public void SaveChanges()
- {
- this.Context.SaveChanges();
- }
#endregion
}
}
diff --git a/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs b/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs
index 7855458d55..82d5a77032 100644
--- a/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs
+++ b/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs
@@ -42,6 +42,9 @@ public IList GetAllByDispositionFile(long fileId)
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(fd => fd.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(x => x.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(fd => fd.DispositionFileId == fileId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/DocumentQueueRepository.cs b/source/backend/dal/Repositories/DocumentQueueRepository.cs
index fa2472427a..e6664d3604 100644
--- a/source/backend/dal/Repositories/DocumentQueueRepository.cs
+++ b/source/backend/dal/Repositories/DocumentQueueRepository.cs
@@ -1,7 +1,10 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
+using Pims.Api.Models.CodeTypes;
using Pims.Core.Extensions;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
@@ -32,16 +35,69 @@ public DocumentQueueRepository(
#region Methods
+ ///
+ /// Attempts to find a queued document via the documentQueueId. Returns null if not found.
+ ///
+ ///
+ ///
+ public PimsDocumentQueue TryGetById(long documentQueueId)
+ {
+
+ return Context.PimsDocumentQueues
+ .AsNoTracking()
+ .FirstOrDefault(dq => dq.DocumentQueueId == documentQueueId);
+ }
+
+ ///
+ /// Add Document to Queue.
+ ///
+ ///
+ ///
+ public PimsDocumentQueue Add(PimsDocumentQueue queuedDocument)
+ {
+ queuedDocument.ThrowIfNull(nameof(queuedDocument));
+
+ // Default values for new queue items.
+ queuedDocument.DocumentQueueStatusTypeCode = DocumentQueueStatusTypes.PENDING.ToString();
+ queuedDocument.DataSourceTypeCode = DataSourceTypes.PIMS.ToString();
+ queuedDocument.MayanError = null;
+
+ // Add
+ Context.PimsDocumentQueues.Add(queuedDocument);
+
+ return queuedDocument;
+ }
+
+ ///
+ /// Find Queue Item for a Document.
+ ///
+ ///
+ ///
+ public PimsDocumentQueue GetByDocumentId(long documentId)
+ {
+ return Context.PimsDocumentQueues.Where(x => x.DocumentId == documentId).FirstOrDefault();
+ }
+
///
/// Updates the queued document in the database.
///
///
///
- public PimsDocumentQueue Update(PimsDocumentQueue queuedDocument)
+ public PimsDocumentQueue Update(PimsDocumentQueue queuedDocument, bool removeDocument = false)
{
queuedDocument.ThrowIfNull(nameof(queuedDocument));
+ var existingQueuedDocument = TryGetById(queuedDocument.DocumentQueueId);
+ if (!removeDocument)
+ {
+ queuedDocument.Document = existingQueuedDocument.Document;
+ }
+
+ queuedDocument.MayanError = queuedDocument.MayanError?.Truncate(4000);
+ queuedDocument.DataSourceTypeCode = existingQueuedDocument.DataSourceTypeCode; // Do not allow the data source to be updated.
+ Context.Entry(existingQueuedDocument).CurrentValues.SetValues(queuedDocument);
queuedDocument = Context.Update(queuedDocument).Entity;
+
return queuedDocument;
}
@@ -53,11 +109,20 @@ public PimsDocumentQueue Update(PimsDocumentQueue queuedDocument)
public bool Delete(PimsDocumentQueue queuedDocument)
{
queuedDocument.ThrowIfNull(nameof(queuedDocument));
-
Context.Remove(queuedDocument);
+
return true;
}
+ public Dictionary GetFileLengthsById(IEnumerable documentQueueIds)
+ {
+ return Context.PimsDocumentQueues
+ .AsNoTracking()
+ .Where(dq => documentQueueIds.Any(dqId => dqId == dq.DocumentQueueId))
+ .Select(dq => new { Key = dq.Internal_Id, Value = dq.Document.Length })
+ .ToDictionary(pair => pair.Key, pair => pair.Value);
+ }
+
///
/// Return a list of documents, filtered by the specified arguments.
///
@@ -65,25 +130,62 @@ public bool Delete(PimsDocumentQueue queuedDocument)
///
public IEnumerable GetAllByFilter(DocumentQueueFilter filter)
{
- var query = Context.PimsDocumentQueues.Where(q => true);
+ var query = Context.PimsDocumentQueues
+ .Include(dq => dq.DocumentNavigation)
+ .ThenInclude(d => d.DocumentType)
+ .Include(dq => dq.DocumentQueueStatusTypeCodeNavigation)
+ .Include(dq => dq.DataSourceTypeCodeNavigation)
+ .Where(q => true).AsNoTracking();
if (filter.DataSourceTypeCode != null)
{
- query.Where(d => d.DataSourceTypeCode == filter.DataSourceTypeCode);
+ query = query.Where(d => d.DataSourceTypeCode == filter.DataSourceTypeCode);
}
- if (filter.DocumentQueueStatusTypeCode != null)
+ if (filter.DocumentQueueStatusTypeCodes != null && filter.DocumentQueueStatusTypeCodes.Length > 0)
{
- query.Where(d => d.DocumentQueueStatusTypeCode == filter.DocumentQueueStatusTypeCode);
+ query = query.Where(d => filter.DocumentQueueStatusTypeCodes.Any(filterStatus => d.DocumentQueueStatusTypeCode == filterStatus));
}
if (filter.DocProcessStartDate != null)
{
- query.Where(d => d.DocProcessStartDt >= filter.DocProcessStartDate);
+ query = query.Where(d => d.DocProcessStartDt >= filter.DocProcessStartDate);
}
if (filter.DocProcessEndDate != null)
{
- query.Where(d => d.DocProcessEndDt <= filter.DocProcessEndDate);
+ query = query.Where(d => d.DocProcessEndDt <= filter.DocProcessEndDate);
}
- return query.ToList();
+ if (filter.MaxDocProcessRetries != null)
+ {
+ query = query.Where(d => d.DocProcessRetries == null || d.DocProcessRetries < filter.MaxDocProcessRetries);
+ }
+
+ // Return the PimsDocumentQueue search results without the file contents - to avoid memory issues.
+ return query.Take(filter.Quantity).Select(dq => new PimsDocumentQueue()
+ {
+ DocumentQueueId = dq.DocumentQueueId,
+ DocumentId = dq.DocumentId,
+ DocumentQueueStatusTypeCode = dq.DocumentQueueStatusTypeCode,
+ DocumentQueueStatusTypeCodeNavigation = dq.DocumentQueueStatusTypeCodeNavigation,
+ DataSourceTypeCode = dq.DataSourceTypeCode,
+ DataSourceTypeCodeNavigation = dq.DataSourceTypeCodeNavigation,
+ DocumentExternalId = dq.DocumentExternalId,
+ DocProcessStartDt = dq.DocProcessStartDt,
+ DocProcessEndDt = dq.DocProcessEndDt,
+ DocProcessRetries = dq.DocProcessRetries,
+ MayanError = dq.MayanError,
+ AppCreateTimestamp = dq.AppCreateTimestamp,
+ AppCreateUserDirectory = dq.AppCreateUserDirectory,
+ AppCreateUserGuid = dq.AppCreateUserGuid,
+ AppCreateUserid = dq.AppCreateUserid,
+ AppLastUpdateTimestamp = dq.AppLastUpdateTimestamp,
+ AppLastUpdateUserDirectory = dq.AppLastUpdateUserDirectory,
+ AppLastUpdateUserGuid = dq.AppLastUpdateUserGuid,
+ AppLastUpdateUserid = dq.AppLastUpdateUserid,
+ DbCreateTimestamp = dq.DbCreateTimestamp,
+ DbCreateUserid = dq.DbCreateUserid,
+ DbLastUpdateTimestamp = dq.DbLastUpdateTimestamp,
+ DbLastUpdateUserid = dq.DbLastUpdateUserid,
+ ConcurrencyControlNumber = dq.ConcurrencyControlNumber,
+ }).ToList();
}
public int DocumentQueueCount(PimsDocumentQueueStatusType pimsDocumentQueueStatusType)
@@ -92,6 +194,7 @@ public int DocumentQueueCount(PimsDocumentQueueStatusType pimsDocumentQueueStatu
{
Context.PimsDocumentQueues.Count();
}
+
return Context.PimsDocumentQueues.Count(d => d.DocumentQueueStatusTypeCode == pimsDocumentQueueStatusType.DocumentQueueStatusTypeCode);
}
diff --git a/source/backend/dal/Repositories/DocumentRepository.cs b/source/backend/dal/Repositories/DocumentRepository.cs
index c9a9716baa..2cdf09989b 100644
--- a/source/backend/dal/Repositories/DocumentRepository.cs
+++ b/source/backend/dal/Repositories/DocumentRepository.cs
@@ -4,9 +4,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pims.Core.Extensions;
-using Pims.Dal.Entities;
-using Pims.Dal.Helpers.Extensions;
using Pims.Core.Security;
+using Pims.Dal.Entities;
namespace Pims.Dal.Repositories
{
@@ -44,6 +43,23 @@ public PimsDocument TryGet(long documentId)
return this.Context.PimsDocuments.AsNoTracking().FirstOrDefault(x => x.DocumentId == documentId);
}
+ public PimsDocument TryGetDocumentRelationships(long documentId)
+ {
+ var documentRelationships = Context.PimsDocuments.AsNoTracking()
+ .Include(d => d.PimsResearchFileDocuments)
+ .Include(d => d.PimsAcquisitionFileDocuments)
+ .Include(d => d.PimsProjectDocuments)
+ .Include(d => d.PimsFormTypes)
+ .Include(d => d.PimsLeaseDocuments)
+ .Include(d => d.PimsPropertyActivityDocuments)
+ .Include(d => d.PimsDispositionFileDocuments)
+ .Where(d => d.DocumentId == documentId)
+ .AsNoTracking()
+ .FirstOrDefault();
+
+ return documentRelationships;
+ }
+
///
/// Adds the passed document to the database.
///
@@ -72,9 +88,10 @@ public PimsDocument Add(PimsDocument document)
public PimsDocument Update(PimsDocument document, bool commitTransaction = true)
{
document.ThrowIfNull(nameof(document));
+ User.ThrowIfNotAuthorized(Permissions.DocumentEdit);
- this.User.ThrowIfNotAuthorized(Permissions.DocumentEdit);
document = Context.Update(document).Entity;
+
return document;
}
@@ -83,7 +100,7 @@ public PimsDocument Update(PimsDocument document, bool commitTransaction = true)
///
///
///
- public bool Delete(PimsDocument document)
+ public bool Delete(PimsDocument document, bool commitTransaction = true)
{
document.ThrowIfNull(nameof(document));
@@ -137,9 +154,27 @@ public bool Delete(PimsDocument document)
Context.Entry(pimsFormTypeDocument).Property(x => x.DocumentId).IsModified = true;
}
- Context.CommitTransaction(); // TODO: required to enforce delete order. Can be removed when cascade deletes are implemented.
+ if (commitTransaction)
+ {
+ Context.CommitTransaction(); // TODO: required to enforce delete order. Can be removed when cascade deletes are implemented.
+ }
Context.PimsDocuments.Remove(new PimsDocument() { Internal_Id = document.Internal_Id });
+
+ return true;
+ }
+
+ ///
+ /// Deletes the passed document from the database.
+ ///
+ ///
+ ///
+ public bool DeleteDocument(PimsDocument document)
+ {
+ document.ThrowIfNull(nameof(document));
+
+ Context.PimsDocuments.Remove(document);
+
return true;
}
diff --git a/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs b/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs
index 45c14d19ac..8ff5bf4e65 100644
--- a/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs
+++ b/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs
@@ -9,9 +9,18 @@ namespace Pims.Dal.Repositories
///
public interface IDocumentQueueRepository : IRepository
{
+
+ PimsDocumentQueue TryGetById(long documentQueueId);
+
+ PimsDocumentQueue Add(PimsDocumentQueue queuedDocument);
+
+ PimsDocumentQueue GetByDocumentId(long documentId);
+
IEnumerable GetAllByFilter(DocumentQueueFilter filter);
- PimsDocumentQueue Update(PimsDocumentQueue queuedDocument);
+ Dictionary GetFileLengthsById(IEnumerable documentQueueIds);
+
+ PimsDocumentQueue Update(PimsDocumentQueue queuedDocument, bool removeDocument = false);
bool Delete(PimsDocumentQueue queuedDocument);
diff --git a/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs b/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs
index 3be4e2b302..dd53b48872 100644
--- a/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs
+++ b/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs
@@ -13,8 +13,12 @@ public interface IDocumentRepository : IRepository
PimsDocument Update(PimsDocument document, bool commitTransaction = true);
- bool Delete(PimsDocument document);
+ bool Delete(PimsDocument document, bool commitTransaction = true);
+
+ bool DeleteDocument(PimsDocument document);
int DocumentRelationshipCount(long documentId);
+
+ PimsDocument TryGetDocumentRelationships(long documentId);
}
}
diff --git a/source/backend/dal/Repositories/LeaseRepository.cs b/source/backend/dal/Repositories/LeaseRepository.cs
index 0748d9e223..f241527fac 100644
--- a/source/backend/dal/Repositories/LeaseRepository.cs
+++ b/source/backend/dal/Repositories/LeaseRepository.cs
@@ -782,6 +782,9 @@ public IList GetAllLeaseDocuments(long leaseId)
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(ad => ad.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(x => x.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(x => x.LeaseId == leaseId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/ProjectRepository.cs b/source/backend/dal/Repositories/ProjectRepository.cs
index 5fb6c42f84..223ceee44f 100644
--- a/source/backend/dal/Repositories/ProjectRepository.cs
+++ b/source/backend/dal/Repositories/ProjectRepository.cs
@@ -184,6 +184,9 @@ public IList GetAllProjectDocuments(long projectId)
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(ad => ad.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(x => x.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(x => x.ProjectId == projectId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/PropertyActivityFileDocumentRepository.cs b/source/backend/dal/Repositories/PropertyActivityFileDocumentRepository.cs
index b07156a10d..9d4a08263a 100644
--- a/source/backend/dal/Repositories/PropertyActivityFileDocumentRepository.cs
+++ b/source/backend/dal/Repositories/PropertyActivityFileDocumentRepository.cs
@@ -41,6 +41,9 @@ public IList GetAllByPropertyActivity(long propert
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(ad => ad.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(x => x.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(ad => ad.PimsPropertyActivityId == propertyActivityId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/ResearchFileDocumentRepository.cs b/source/backend/dal/Repositories/ResearchFileDocumentRepository.cs
index 6759964926..4aa9fcd2f5 100644
--- a/source/backend/dal/Repositories/ResearchFileDocumentRepository.cs
+++ b/source/backend/dal/Repositories/ResearchFileDocumentRepository.cs
@@ -41,6 +41,9 @@ public IList GetAllByResearchFile(long fileId)
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(ad => ad.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(ad => ad.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(ad => ad.ResearchFileId == fileId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/UserRepository.cs b/source/backend/dal/Repositories/UserRepository.cs
index 4e4902ffd1..3150d21730 100644
--- a/source/backend/dal/Repositories/UserRepository.cs
+++ b/source/backend/dal/Repositories/UserRepository.cs
@@ -7,12 +7,12 @@
using Microsoft.Extensions.Options;
using Pims.Core.Extensions;
using Pims.Core.Http.Configuration;
+using Pims.Core.Security;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Comparers;
using Pims.Dal.Entities.Models;
using Pims.Dal.Exceptions;
using Pims.Dal.Helpers.Extensions;
-using Pims.Core.Security;
namespace Pims.Dal.Repositories
{
diff --git a/source/backend/entities/Models/DocumentQueueFilter.cs b/source/backend/entities/Models/DocumentQueueFilter.cs
index c9d43c5e61..d6d8259823 100644
--- a/source/backend/entities/Models/DocumentQueueFilter.cs
+++ b/source/backend/entities/Models/DocumentQueueFilter.cs
@@ -14,7 +14,7 @@ public class DocumentQueueFilter : PageFilter
///
/// get/set - The status of the document in the queue, such as 'Pending'.
///
- public string DocumentQueueStatusTypeCode { get; set; }
+ public string[] DocumentQueueStatusTypeCodes { get; set; }
///
/// get/set - The date/time that processing of the document started.
@@ -26,6 +26,16 @@ public class DocumentQueueFilter : PageFilter
///
public DateTime? DocProcessEndDate { get; set; }
+ ///
+ /// get/set - The maximum number of times that the system has attempted to upload the document after the initial failure.
+ ///
+ public int? MaxDocProcessRetries { get; set; }
+
+ ///
+ /// get/set - The maximum file size to return from the filter.
+ ///
+ public int? MaxFileSize { get; set; }
+
#endregion
#region Constructors
diff --git a/source/backend/entities/Partials/DocumentQueueStatusType.cs b/source/backend/entities/Partials/DocumentQueueStatusType.cs
new file mode 100644
index 0000000000..9fd14e1cf7
--- /dev/null
+++ b/source/backend/entities/Partials/DocumentQueueStatusType.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Pims.Dal.Entities
+{
+ ///
+ /// PimsDocumentQueueStatusType class, provides an entity for the datamodel to manage document queue status types.
+ ///
+ public partial class PimsDocumentQueueStatusType : ITypeEntity
+ {
+ #region Properties
+
+ ///
+ /// get/set - Primary key to identify disposition type.
+ ///
+ [NotMapped]
+ public string Id { get => DocumentQueueStatusTypeCode; set => DocumentQueueStatusTypeCode = value; }
+ #endregion
+
+ #region Constructors
+
+ public PimsDocumentQueueStatusType() { }
+
+ ///
+ /// Create a new instance of a PimsDocumentQueueStatusType class.
+ ///
+ ///
+ public PimsDocumentQueueStatusType(string id)
+ {
+ Id = id;
+ }
+ #endregion
+ }
+}
diff --git a/source/backend/entities/ef/PimsDocument.cs b/source/backend/entities/ef/PimsDocument.cs
index bf16d7a285..1ebb797281 100644
--- a/source/backend/entities/ef/PimsDocument.cs
+++ b/source/backend/entities/ef/PimsDocument.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
diff --git a/source/backend/entities/ef/PimsDocumentQueue.cs b/source/backend/entities/ef/PimsDocumentQueue.cs
index a4dc9c20a1..8efe09b4a7 100644
--- a/source/backend/entities/ef/PimsDocumentQueue.cs
+++ b/source/backend/entities/ef/PimsDocumentQueue.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -29,7 +29,7 @@ public partial class PimsDocumentQueue
public long? DocumentId { get; set; }
///
- /// Code value that represents the current status of the document as it is processed by PIMS/MAYAN
+ /// Code value that represents the current status of the document as it is processed by PIMS/MAYAN.
///
[Required]
[Column("DOCUMENT_QUEUE_STATUS_TYPE_CODE")]
@@ -65,7 +65,7 @@ public partial class PimsDocumentQueue
public DateTime? DocProcessStartDt { get; set; }
///
- /// When the document?s processing finishes, this will be populated
+ /// When the document?s processing finishes, this will be populated.
///
[Column("DOC_PROCESS_END_DT", TypeName = "datetime")]
public DateTime? DocProcessEndDt { get; set; }
@@ -90,7 +90,7 @@ public partial class PimsDocumentQueue
public byte[] Document { get; set; }
///
- /// Application code is responsible for retrieving the row and then incrementing the value of the CONCURRENCY_CONTROL_NUMBER column by one prior to issuing an update. If this is done then the update will succeed, provided that the row was not updated by any o
+ /// Application code is responsible for retrieving the row and then incrementing the value of the CONCURRENCY_CONTROL_NUMBER column by one prior to issuing an update. If this is done then the update will succeed, provided that the row was not updated by any o.
///
[Column("CONCURRENCY_CONTROL_NUMBER")]
public long ConcurrencyControlNumber { get; set; }
diff --git a/source/backend/geocoder/Pims.Geocoder.csproj b/source/backend/geocoder/Pims.Geocoder.csproj
index cceef3324b..df67a1e506 100644
--- a/source/backend/geocoder/Pims.Geocoder.csproj
+++ b/source/backend/geocoder/Pims.Geocoder.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/source/backend/keycloak/Pims.Keycloak.csproj b/source/backend/keycloak/Pims.Keycloak.csproj
index 1a4db4b783..90f26d1ebb 100644
--- a/source/backend/keycloak/Pims.Keycloak.csproj
+++ b/source/backend/keycloak/Pims.Keycloak.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/source/backend/ltsa/Pims.Ltsa.csproj b/source/backend/ltsa/Pims.Ltsa.csproj
index ecd2ca6fef..e471010bca 100644
--- a/source/backend/ltsa/Pims.Ltsa.csproj
+++ b/source/backend/ltsa/Pims.Ltsa.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/source/backend/scheduler/Configuration/QueryProcessingDocumentsJobOptions.cs b/source/backend/scheduler/Configuration/QueryProcessingDocumentsJobOptions.cs
new file mode 100644
index 0000000000..3e30f8034d
--- /dev/null
+++ b/source/backend/scheduler/Configuration/QueryProcessingDocumentsJobOptions.cs
@@ -0,0 +1,21 @@
+namespace Pims.Scheduler.Http.Configuration
+{
+ ///
+ /// QueryProcessingDocumentsJobOptions class, provides a way to store job configuration.
+ ///
+ public class QueryProcessingDocumentsJobOptions
+ {
+ #region Properties
+
+ ///
+ /// get/set - the number of queued documents to pull in a single operation - affects the number of documents that will be uploaded in a single job run.
+ ///
+ public int? BatchSize { get; set; }
+
+ ///
+ /// get/set - the maximum number of minutes a document can be processing for before the upload is considered to be a failure.
+ ///
+ public int MaxProcessingMinutes { get; set; }
+ #endregion
+ }
+}
diff --git a/source/backend/scheduler/Configuration/RetryQueuedDocumentsJobOptions.cs b/source/backend/scheduler/Configuration/RetryQueuedDocumentsJobOptions.cs
new file mode 100644
index 0000000000..36686d8437
--- /dev/null
+++ b/source/backend/scheduler/Configuration/RetryQueuedDocumentsJobOptions.cs
@@ -0,0 +1,21 @@
+namespace Pims.Scheduler.Http.Configuration
+{
+ ///
+ /// RetryQueuedDocumentsJobOptions class, provides a way to store job configuration.
+ ///
+ public class RetryQueuedDocumentsJobOptions
+ {
+ #region Properties
+
+ ///
+ /// get/set - the number of queued documents to pull in a single operation - affects the number of documents that will be uploaded in a single job run.
+ ///
+ public int? BatchSize { get; set; }
+
+ ///
+ /// get/set - the file size, in bytes, that will be processed in a single job run.
+ ///
+ public int? MaxFileSize { get; set; }
+ #endregion
+ }
+}
diff --git a/source/backend/scheduler/Configuration/UploadQueuedDocumentsJobOptions.cs b/source/backend/scheduler/Configuration/UploadQueuedDocumentsJobOptions.cs
new file mode 100644
index 0000000000..0c7dddc252
--- /dev/null
+++ b/source/backend/scheduler/Configuration/UploadQueuedDocumentsJobOptions.cs
@@ -0,0 +1,21 @@
+namespace Pims.Scheduler.Http.Configuration
+{
+ ///
+ /// UploadQueuedDocumentsJobOptions class, provides a way to store job configuration.
+ ///
+ public class UploadQueuedDocumentsJobOptions
+ {
+ #region Properties
+
+ ///
+ /// get/set - the number of queued documents to pull in a single operation - affects the number of documents that will be uploaded in a single job run.
+ ///
+ public int? BatchSize { get; set; }
+
+ ///
+ /// get/set - the file size, in bytes, that will be processed in a single job run.
+ ///
+ public int? MaxFileSize { get; set; }
+ #endregion
+ }
+}
diff --git a/source/backend/scheduler/Models/DocumentQueueResponseModel.cs b/source/backend/scheduler/Models/DocumentQueueResponseModel.cs
new file mode 100644
index 0000000000..c7fc1b699f
--- /dev/null
+++ b/source/backend/scheduler/Models/DocumentQueueResponseModel.cs
@@ -0,0 +1,11 @@
+using Pims.Api.Models.CodeTypes;
+
+namespace Pims.Scheduler.Models
+{
+ public class DocumentQueueResponseModel
+ {
+ public DocumentQueueStatusTypes DocumentQueueStatus { get; set; }
+
+ public string Message { get; set; }
+ }
+}
diff --git a/source/backend/scheduler/Models/ScheduledTaskResponseModel.cs b/source/backend/scheduler/Models/ScheduledTaskResponseModel.cs
new file mode 100644
index 0000000000..5b2e6c7631
--- /dev/null
+++ b/source/backend/scheduler/Models/ScheduledTaskResponseModel.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace Pims.Scheduler.Models
+{
+ public class ScheduledTaskResponseModel
+ {
+ public TaskResponseStatusTypes Status { get; set; }
+
+ public string Message { get; set; }
+
+ public IEnumerable DocumentQueueResponses { get; set; }
+ }
+}
diff --git a/source/backend/scheduler/Models/SearchQueuedDocumentsResponseModel.cs b/source/backend/scheduler/Models/SearchQueuedDocumentsResponseModel.cs
new file mode 100644
index 0000000000..f37ee8dfe4
--- /dev/null
+++ b/source/backend/scheduler/Models/SearchQueuedDocumentsResponseModel.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
+
+namespace Pims.Scheduler.Models
+{
+ public class SearchQueuedDocumentsResponseModel
+ {
+ public ExternalResponse> SearchResults { get; set; }
+
+ public ScheduledTaskResponseModel ScheduledTaskResponseModel { get; set; }
+ }
+}
diff --git a/source/backend/scheduler/Models/TaskResponseStatusTypes.cs b/source/backend/scheduler/Models/TaskResponseStatusTypes.cs
new file mode 100644
index 0000000000..7f118cdba9
--- /dev/null
+++ b/source/backend/scheduler/Models/TaskResponseStatusTypes.cs
@@ -0,0 +1,10 @@
+namespace Pims.Scheduler.Models
+{
+ public enum TaskResponseStatusTypes
+ {
+ ERROR,
+ SUCCESS,
+ PARTIAL,
+ SKIPPED,
+ }
+}
diff --git a/source/backend/scheduler/Pims.Scheduler.csproj b/source/backend/scheduler/Pims.Scheduler.csproj
index e4f4993886..f8553b3712 100644
--- a/source/backend/scheduler/Pims.Scheduler.csproj
+++ b/source/backend/scheduler/Pims.Scheduler.csproj
@@ -2,7 +2,7 @@
true
- 16BC0468-78F6-4C91-87DA-7403C919E646
+ {AC4336C5-5631-4D9D-B78F-6C2DF79A6F1F}
net8.0
diff --git a/source/backend/scheduler/Repositories/Interfaces/IPimsDocumentQueueRepository.cs b/source/backend/scheduler/Repositories/Interfaces/IPimsDocumentQueueRepository.cs
new file mode 100644
index 0000000000..7142760d1b
--- /dev/null
+++ b/source/backend/scheduler/Repositories/Interfaces/IPimsDocumentQueueRepository.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
+using Pims.Dal.Entities.Models;
+
+namespace Pims.Scheduler.Repositories
+{
+ ///
+ /// IPimsDocumentQueueRepository interface, defines the functionality for a repository that interacts with the pims document queue api.
+ ///
+ public interface IPimsDocumentQueueRepository
+ {
+ Task> GetById(long documentQueueId);
+
+ Task> UploadQueuedDocument(DocumentQueueModel document);
+
+ Task> PollQueuedDocument(DocumentQueueModel document);
+
+ Task> UpdateQueuedDocument(long documentQueueId, DocumentQueueModel document);
+
+ Task>> SearchQueuedDocumentsAsync(DocumentQueueFilter filter);
+ }
+}
diff --git a/source/backend/scheduler/Repositories/Interfaces/IPimsDocumentRepository.cs b/source/backend/scheduler/Repositories/Interfaces/IPimsDocumentRepository.cs
deleted file mode 100644
index 7505e3576d..0000000000
--- a/source/backend/scheduler/Repositories/Interfaces/IPimsDocumentRepository.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Pims.Api.Models.Concepts.Document;
-using Pims.Api.Models.Requests.Http;
-using Pims.Dal.Entities.Models;
-
-namespace Pims.Scheduler.Repositories.Pims
-{
- ///
- /// IPimsDocumentQueueRepository interface, defines the functionality for a pims repository.
- ///
- public interface IPimsDocumentQueueRepository
- {
- Task>> SearchQueuedDocumentsAsync(DocumentQueueFilter filter);
- }
-}
diff --git a/source/backend/scheduler/Repositories/PimsBaseRepository.cs b/source/backend/scheduler/Repositories/PimsBaseRepository.cs
index 6cdccd99cc..5f822e7eb3 100644
--- a/source/backend/scheduler/Repositories/PimsBaseRepository.cs
+++ b/source/backend/scheduler/Repositories/PimsBaseRepository.cs
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Text.Json;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Core.Api.Repositories.Rest;
namespace Pims.Scheduler.Repositories
@@ -14,10 +16,12 @@ public abstract class PimsBaseRepository : BaseRestRepository
///
/// Injected Logger Provider.
/// Injected Httpclient factory.
+ /// Injected app-wide json options.
protected PimsBaseRepository(
ILogger logger,
- IHttpClientFactory httpClientFactory)
- : base(logger, httpClientFactory)
+ IHttpClientFactory httpClientFactory,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, jsonOptions)
{
}
diff --git a/source/backend/scheduler/Repositories/PimsDocumentQueueRepository.cs b/source/backend/scheduler/Repositories/PimsDocumentQueueRepository.cs
new file mode 100644
index 0000000000..f78ef001f5
--- /dev/null
+++ b/source/backend/scheduler/Repositories/PimsDocumentQueueRepository.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
+using Pims.Core.Extensions;
+using Pims.Core.Http;
+using Pims.Dal.Entities.Models;
+using Pims.Scheduler.Http.Configuration;
+
+namespace Pims.Scheduler.Repositories
+{
+ ///
+ /// PimsDocumentQueueRepository provides document access from the PIMS document queue api.
+ ///
+ public class PimsDocumentQueueRepository : PimsBaseRepository, IPimsDocumentQueueRepository
+ {
+ private static readonly JsonSerializerOptions SerializerOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
+
+ private readonly IOpenIdConnectRequestClient _authRepository;
+ private readonly IOptionsMonitor _configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Injected Logger Provider.
+ /// Injected Httpclient factory.
+ /// Injected repository that handles authentication.
+ /// The injected configuration provider.
+ /// The injected json options.
+ public PimsDocumentQueueRepository(
+ ILogger logger,
+ IHttpClientFactory httpClientFactory,
+ IOpenIdConnectRequestClient authRepository,
+ IOptionsMonitor configuration,
+ IOptions jsonOptions)
+ : base(logger, httpClientFactory, jsonOptions)
+ {
+ _authRepository = authRepository;
+ _configuration = configuration;
+ }
+
+ ///
+ /// Polls the upload status in mayan of a queued document using the provided document model.
+ ///
+ /// The document to poll.
+ /// A task that represents the asynchronous operation. The result is an external response containing the document queue model and status information.
+ public async Task> PollQueuedDocument(DocumentQueueModel document)
+ {
+ _logger.LogDebug("polling queued document with id {documentId}", document.Id);
+
+ string authenticationToken = await _authRepository.RequestAccessToken();
+
+ Uri endpoint = new($"{_configuration.CurrentValue.Uri}/documents/queue/{document.Id}/poll");
+
+ string serializedFilter = JsonSerializer.Serialize(document, SerializerOptions);
+ using var content = new StringContent(serializedFilter, Encoding.UTF8, "application/json");
+
+ var response = await PostAsync(endpoint, content, authenticationToken);
+ _logger.LogDebug("queued document poll for document with id {documentId} complete with status: {response}", document.Id, response.Serialize());
+
+ return response;
+ }
+
+ ///
+ /// Uploads a queued document to the specified endpoint.
+ ///
+ /// The document queue model containing the document details.
+ /// A task that represents the asynchronous operation and returns an external response containing the status of the upload.
+ public async Task> UploadQueuedDocument(DocumentQueueModel document)
+ {
+ _logger.LogDebug("uploading queued document with id {documentId}", document.Id);
+
+ string authenticationToken = await _authRepository.RequestAccessToken();
+
+ Uri endpoint = new($"{_configuration.CurrentValue.Uri}/documents/queue/{document.Id}/upload");
+
+ string serializedFilter = JsonSerializer.Serialize(document, SerializerOptions);
+ using var content = new StringContent(serializedFilter, Encoding.UTF8, "application/json");
+
+ var response = await PostAsync(endpoint, content, authenticationToken);
+ _logger.LogDebug("queued document upload for document with id {documentId} complete with status: {response}", document.Id, response.Serialize());
+
+ return response;
+ }
+
+ ///
+ /// Updates an existing queued document.
+ ///
+ /// The ID of the document to update.
+ /// The updated document details.
+ /// The result of the update operation.
+ public async Task> UpdateQueuedDocument(long documentQueueId, DocumentQueueModel document)
+ {
+ _logger.LogDebug("updating queued document with id {documentId}", documentQueueId);
+
+ string authenticationToken = await _authRepository.RequestAccessToken();
+
+ Uri endpoint = new($"{_configuration.CurrentValue.Uri}/documents/queue/{documentQueueId}");
+
+ string serializedFilter = JsonSerializer.Serialize(document, SerializerOptions);
+ using var content = new StringContent(serializedFilter, Encoding.UTF8, "application/json");
+
+ var response = await PutAsync(endpoint, content, authenticationToken);
+ _logger.LogDebug("queued document update for document with id {documentId} complete with {response}", documentQueueId, response.Serialize());
+
+ return response;
+ }
+
+ ///
+ /// Updates an existing queued document.
+ ///
+ /// The ID of the document to update.
+ /// The result of the update operation.
+ public async Task> GetById(long documentQueueId)
+ {
+ _logger.LogDebug("getting queued document with id {documentId}", documentQueueId);
+
+ string authenticationToken = await _authRepository.RequestAccessToken();
+
+ Uri endpoint = new($"{_configuration.CurrentValue.Uri}/documents/queue/{documentQueueId}");
+
+ var response = await GetAsync(endpoint, authenticationToken);
+ _logger.LogDebug("queued document retrieval for document with id {documentId} complete with {response}", documentQueueId, response.Serialize());
+
+ return response;
+ }
+
+ ///
+ /// Searches for queued documents based on the provided filter.
+ ///
+ /// The filter to apply to the search.
+ /// A task that represents the asynchronous operation, returning a list of document queue models.
+ public async Task>> SearchQueuedDocumentsAsync(DocumentQueueFilter filter)
+ {
+ _logger.LogDebug("Getting filtered list of queued documents by {filter}", filter);
+
+ string authenticationToken = await _authRepository.RequestAccessToken();
+
+ Uri endpoint = new($"{_configuration.CurrentValue.Uri}/documents/queue/search");
+
+ string serializedFilter = JsonSerializer.Serialize(filter, SerializerOptions);
+ using var content = new StringContent(serializedFilter, Encoding.UTF8, "application/json");
+
+ var response = await PostAsync>(endpoint, content, authenticationToken);
+ _logger.LogDebug("Retrieved list of queued documents based on {filter}, {response} ", filter.Serialize(), response.Serialize());
+
+ return response;
+ }
+ }
+}
diff --git a/source/backend/scheduler/Repositories/PimsRepository.cs b/source/backend/scheduler/Repositories/PimsRepository.cs
deleted file mode 100644
index f6b2b061c6..0000000000
--- a/source/backend/scheduler/Repositories/PimsRepository.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Pims.Api.Models.Concepts.Document;
-using Pims.Api.Models.Requests.Http;
-using Pims.Core.Http;
-using Pims.Dal.Entities.Models;
-using Pims.Scheduler.Http.Configuration;
-
-namespace Pims.Scheduler.Repositories.Pims
-{
- ///
- /// PimsDocumentQueueRepository provides document access from the PIMS document queue api.
- ///
- public class PimsDocumentQueueRepository : PimsBaseRepository, IPimsDocumentQueueRepository
- {
- private readonly IOpenIdConnectRequestClient _authRepository;
- private readonly IOptionsMonitor _configuration;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Injected Logger Provider.
- /// Injected Httpclient factory.
- /// Injected repository that handles authentication.
- /// The injected configuration provider.
- public PimsDocumentQueueRepository(
- ILogger logger,
- IHttpClientFactory httpClientFactory,
- IOpenIdConnectRequestClient authRepository,
- IOptionsMonitor configuration)
- : base(logger, httpClientFactory)
- {
- _authRepository = authRepository;
- _configuration = configuration;
- }
-
- public async Task>> SearchQueuedDocumentsAsync(DocumentQueueFilter filter)
- {
- _logger.LogDebug("Getting filtered list of queued documents by {filter}", filter);
-
- string authenticationToken = await _authRepository.RequestAccessToken();
-
- Uri endpoint = new($"{_configuration.CurrentValue.Uri}/documents/queue/search");
-
- var response = await GetAsync>(endpoint, authenticationToken);
- _logger.LogDebug($"Retrieved list of queued documents based on {filter} ", filter);
-
- return response;
- }
- }
-}
diff --git a/source/backend/scheduler/Scheduler/JobRescheduler.cs b/source/backend/scheduler/Scheduler/JobRescheduler.cs
index 791942711c..03e480f4ec 100644
--- a/source/backend/scheduler/Scheduler/JobRescheduler.cs
+++ b/source/backend/scheduler/Scheduler/JobRescheduler.cs
@@ -42,16 +42,10 @@ public void LoadSchedules(JobScheduleOptions options)
throw new ConfigurationException($"Unable to find TimeZoneInfo : {timezoneId}");
}
- var cron = scheduling.Cron;
- if (cron == null)
- {
- throw new ConfigurationException($"Cron is required");
- }
-
_recurringJobManager.AddOrUpdate(
recurringJob.Id,
recurringJob.Job,
- scheduling.Cron,
+ scheduling.Cron ?? recurringJob.Cron,
new RecurringJobOptions() { TimeZone = timezone });
}
}
diff --git a/source/backend/scheduler/Services/DocumentQueueService.cs b/source/backend/scheduler/Services/DocumentQueueService.cs
index 6e2c2a01c3..5f1bf9e14b 100644
--- a/source/backend/scheduler/Services/DocumentQueueService.cs
+++ b/source/backend/scheduler/Services/DocumentQueueService.cs
@@ -1,8 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
using Pims.Core.Api.Services;
-using Pims.Scheduler.Repositories.Pims;
+using Pims.Core.Extensions;
+using Pims.Dal.Entities.Models;
+using Pims.Scheduler.Http.Configuration;
+using Pims.Scheduler.Models;
+using Pims.Scheduler.Repositories;
namespace Pims.Scheduler.Services
{
@@ -10,20 +20,165 @@ public class DocumentQueueService : BaseService, IDocumentQueueService
{
private readonly ILogger _logger;
private readonly IPimsDocumentQueueRepository _pimsDocumentQueueRepository;
+ private readonly IOptionsMonitor _uploadQueuedDocumentsJobOptions;
+ private readonly IOptionsMonitor _queryProcessingDocumentsJobOptions;
+ private readonly IOptionsMonitor