Skip to content

Commit

Permalink
Merge pull request #229 from dlcs/feature/201/handle_space_assets_posted
Browse files Browse the repository at this point in the history
Impl #201 - Create Space if manifest asset posted w/o space
  • Loading branch information
p-kaczynski authored Jan 17, 2025
2 parents b1637a3 + 67528d3 commit 6058b79
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
using DLCS.Exceptions;
using DLCS.Models;
using FakeItEasy;
using IIIF.Presentation.V3;
using IIIF.Serialisation;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Models.API.General;
Expand Down Expand Up @@ -62,10 +62,11 @@ public ModifyManifestAssetCreationTests(StorageFixture storageFixture, Presentat
}

[Fact]
public async Task CreateManifest_BadRequest_WhenNoSpaceHeader()
public async Task CreateManifest_CreateSpace_ForSpacelessAssets_WhenNoSpaceHeader()
{
const string postedAssetId = "theAssetId";
// Arrange
var slug = nameof(CreateManifest_BadRequest_WhenNoSpaceHeader);
var slug = nameof(CreateManifest_CreateSpace_ForSpacelessAssets_WhenNoSpaceHeader);
var manifest = new PresentationManifest
{
Parent = $"http://localhost/1/collections/{RootCollection.Id}",
Expand All @@ -78,7 +79,7 @@ public async Task CreateManifest_BadRequest_WhenNoSpaceHeader()
{
CanvasId = "https://iiif.example/manifest.json"
},
Asset = new JObject()
Asset = new(new JProperty("id", postedAssetId), new JProperty("batch", 123))
}
}
};
Expand All @@ -90,10 +91,22 @@ public async Task CreateManifest_BadRequest_WhenNoSpaceHeader()
var response = await httpClient.AsCustomer().SendAsync(requestMessage);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
var error = await response.ReadAsPresentationResponseAsync<Error>();
error!.Detail.Should().Be("A request with assets requires the space header to be set");
error.ErrorTypeUri.Should().Be("http://localhost/errors/ModifyCollectionType/RequiresSpaceHeader");
response.StatusCode.Should().Be(HttpStatusCode.Created);
response.Headers.Location.Should().NotBeNull();

requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, response.Headers.Location!.ToString());
response = await httpClient.AsCustomer().SendAsync(requestMessage);

response.StatusCode.Should().Be(HttpStatusCode.OK);
var responseManifest = await response.ReadAsPresentationResponseAsync<PresentationManifest>();
responseManifest.Should().NotBeNull();
responseManifest!.PaintedResources.Should().NotBeNull();
responseManifest.PaintedResources!.Count.Should().Be(1);
responseManifest.PaintedResources.Single().Asset!.TryGetValue("@id", out var assetId).Should().BeTrue();
assetId!.Type.Should().Be(JTokenType.String);
assetId!.Value<string>().Should()
.EndWith($"/customers/{Customer}/spaces/{NewlyCreatedSpace}/images/{postedAssetId}");
}

[Fact]
Expand Down Expand Up @@ -195,7 +208,7 @@ public async Task CreateManifest_CorrectlyCreatesAssetRequests_WithSpace()
var savedS3 =
await amazonS3.GetObjectAsync(LocalStackFixture.StorageBucketName,
$"{Customer}/manifests/{dbManifest.Id}");
var s3Manifest = savedS3.ResponseStream.FromJsonStream<IIIF.Presentation.V3.Manifest>();
var s3Manifest = savedS3.ResponseStream.FromJsonStream<Manifest>();
s3Manifest.Id.Should().EndWith(dbManifest.Id);
s3Manifest.Items.Should().BeNull();
}
Expand Down Expand Up @@ -297,7 +310,7 @@ public async Task CreateManifest_CorrectlyCreatesAssetRequests_WithoutSpace()
var savedS3 =
await amazonS3.GetObjectAsync(LocalStackFixture.StorageBucketName,
$"{Customer}/manifests/{dbManifest.Id}");
var s3Manifest = savedS3.ResponseStream.FromJsonStream<IIIF.Presentation.V3.Manifest>();
var s3Manifest = savedS3.ResponseStream.FromJsonStream<Manifest>();
s3Manifest.Id.Should().EndWith(dbManifest.Id);
s3Manifest.Items.Should().BeNull();
}
Expand Down Expand Up @@ -392,7 +405,7 @@ await amazonS3.GetObjectAsync(LocalStackFixture.StorageBucketName,
var savedS3 =
await amazonS3.GetObjectAsync(LocalStackFixture.StorageBucketName,
$"{Customer}/manifests/{dbManifest.Id}");
var s3Manifest = savedS3.ResponseStream.FromJsonStream<IIIF.Presentation.V3.Manifest>();
var s3Manifest = savedS3.ResponseStream.FromJsonStream<Manifest>();
s3Manifest.Id.Should().EndWith(dbManifest.Id);
s3Manifest.Items.Should().BeNull();
}
Expand Down
84 changes: 51 additions & 33 deletions src/IIIFPresentation/API/Features/Manifest/ManifestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,24 +192,62 @@ private bool CheckForItemsAndPaintedResources(PresentationManifest presentationM
private async Task<(PresUpdateResult?, DbManifest?)> CreateDatabaseRecordAndIiifCloudServicesInteractions(WriteManifestRequest request,
Collection parentCollection, string? requestedId, CancellationToken cancellationToken)
{
var assets = request.PresentationManifest.PaintedResources?.Select(p => p.Asset).ToList();

if (!request.CreateSpace && !assets.IsNullOrEmpty())
{
return (ErrorHelper.SpaceRequired<PresentationManifest>(), null);
}
const string spaceProperty = "space";
var assets = request.PresentationManifest.PaintedResources?
.Select(p => p.Asset)
.OfType<JObject>()
.ToList() ?? [];

var manifestSpace = request.PresentationManifest.Space;

int? spaceId = null;

var manifestId = requestedId ?? await GenerateUniqueManifestId(request, cancellationToken);
if (manifestId == null) return (ErrorHelper.CannotGenerateUniqueId<PresentationManifest>(), null);

if (request.CreateSpace || assets.Count > 0)
{
if (assets.Any(a => !a.HasValues))
return (ErrorHelper.CouldNotRetrieveAssetId<PresentationManifest>(), null);

var assetsWithoutSpaces = assets.Where(a => !a.TryGetValue(spaceProperty, out _)).ToArray();
if (request.CreateSpace || (string.IsNullOrEmpty(manifestSpace) && assetsWithoutSpaces.Length > 0))
{
// Either you want a space or we detected you need a space regardless
spaceId = await CreateSpace(request.CustomerId, manifestId, cancellationToken);
if (!spaceId.HasValue)
return (ErrorHelper.ErrorCreatingSpace<PresentationManifest>(), null);

foreach (var asset in assetsWithoutSpaces)
asset.Add(spaceProperty, spaceId.Value);
}
}

var timeStamp = DateTime.UtcNow;

var spaceIdTask = CreateSpaceIfRequired(request.CustomerId, manifestId, request.CreateSpace, cancellationToken);
if (!assets.IsNullOrEmpty())
{
try
{
var batches = await dlcsApiClient.IngestAssets(request.CustomerId,
assets,
cancellationToken);

await batches.AddBatchesToDatabase(request.CustomerId, manifestId, dbContext, cancellationToken);
}
catch (DlcsException exception)
{
logger.LogError(exception, "Error creating batch request for customer {CustomerId}", request.CustomerId);
return (PresUpdateResult.Failure(exception.Message, ModifyCollectionType.DlcsException,
WriteResult.Error), null);
}
}

var (canvasPaintingsError, canvasPaintings) =
await canvasPaintingResolver.GenerateCanvasPaintings(request.CustomerId, request.PresentationManifest,
await spaceIdTask, cancellationToken);
spaceId, cancellationToken);
if (canvasPaintingsError != null) return (canvasPaintingsError, null);

var timeStamp = DateTime.UtcNow;
var dbManifest = new DbManifest
{
Id = manifestId,
Expand All @@ -225,42 +263,22 @@ await canvasPaintingResolver.GenerateCanvasPaintings(request.CustomerId, request
Slug = request.PresentationManifest.Slug!,
Canonical = true,
Type = ResourceType.IIIFManifest,
Parent = parentCollection.Id,
Parent = parentCollection.Id
}
],
CanvasPaintings = canvasPaintings,
SpaceId = await spaceIdTask
SpaceId = spaceId
};

await dbContext.AddAsync(dbManifest, cancellationToken);

if (!assets.IsNullOrEmpty())
{
try
{
var batches = await dlcsApiClient.IngestAssets(request.CustomerId,
assets,
cancellationToken);

await batches.AddBatchesToDatabase(dbManifest, dbContext, cancellationToken);
}
catch (DlcsException exception)
{
logger.LogError(exception, "Error creating batch request for customer {CustomerId}", request.CustomerId);
return (PresUpdateResult.Failure(exception.Message, ModifyCollectionType.DlcsException,
WriteResult.Error), null);
}
}

var saveErrors = await SaveAndPopulateEntity(request, dbManifest, cancellationToken);
return (saveErrors, dbManifest);
}

private async Task<int?> CreateSpaceIfRequired(int customerId, string manifestId, bool createSpace,
private async Task<int?> CreateSpace(int customerId, string manifestId,
CancellationToken cancellationToken)
{
if (!createSpace) return null;

logger.LogDebug("Creating new space for customer {Customer}", customerId);
var newSpace =
await dlcsApiClient.CreateSpace(customerId, ManifestX.GetDefaultSpaceName(manifestId), cancellationToken);
Expand Down
10 changes: 5 additions & 5 deletions src/IIIFPresentation/API/Helpers/BatchHelper.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Core.Helpers;
using Models.Database.Collections;
using Models.Database.General;
using Repository;
using Batch = DLCS.Models.Batch;

namespace API.Helpers;

Expand All @@ -10,16 +10,16 @@ public static class BatchHelper
/// <summary>
/// This method adds, but does not save batches to the batches table
/// </summary>
public static async Task AddBatchesToDatabase(this List<DLCS.Models.Batch> batches,
Manifest manifest, PresentationContext dbContext, CancellationToken cancellationToken = default)
public static async Task AddBatchesToDatabase(this List<Batch> batches,
int customerId, string manifestId, PresentationContext dbContext, CancellationToken cancellationToken = default)
{
var dbBatches = batches.Select(b => new Models.Database.General.Batch
{
Id = Convert.ToInt32(b.ResourceId!.GetLastPathElement()),
CustomerId = manifest.CustomerId,
CustomerId = customerId,
Submitted = b.Submitted.ToUniversalTime(),
Status = BatchStatus.Ingesting,
ManifestId = manifest.Id
ManifestId = manifestId
});

await dbContext.Batches.AddRangeAsync(dbBatches, cancellationToken);
Expand Down

0 comments on commit 6058b79

Please sign in to comment.