Skip to content

Commit

Permalink
Merge pull request #68 from dlcs/fix/thumbnailExpectsExternalResource
Browse files Browse the repository at this point in the history
Thumbnail can't handle IIIF style thumbnail on POST
  • Loading branch information
JackLewis-digirati authored Oct 18, 2024
2 parents 0802571 + 36d0f2a commit b750993
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 69 deletions.
53 changes: 33 additions & 20 deletions src/IIIFPresentation/API.Tests/Integration/ModifyCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task CreateCollection_CreatesCollection_WhenAllValuesProvided()
Label = new LanguageMap("en", ["test collection"]),
Slug = "programmatic-child",
Parent = parent,
Thumbnail = "some/thumbnail",
PresentationThumbnail = "some/thumbnail",
Tags = "some, tags",
ItemsOrder = 1,
};
Expand Down Expand Up @@ -98,20 +98,32 @@ public async Task CreateCollection_CreatesCollection_WhenAllValuesProvided()
public async Task CreateCollection_CreatesCollection_WhenIsStorageCollectionFalse()
{
// Arrange
var collection = new UpsertFlatCollection()
{
Behavior = new List<string>()
{
Behavior.IsPublic
},
Label = new LanguageMap("en", ["test collection"]),
Slug = "iiif-child",
Parent = parent,
Tags = "some, tags",
ItemsOrder = 1,
};
var collection = $@"{{
""type"": ""Collection"",
""behavior"": [
""public-iiif""
],
""label"": {{
""en"": [
""iiif post""
]
}},
""slug"": ""iiif-child"",
""parent"": ""{parent}"",
""tags"": ""some, tags"",
""itemsOrder"": 1,
""thumbnail"": [
{{
""id"": ""https://example.org/img/thumb.jpg"",
""type"": ""Image"",
""format"": ""image/jpeg"",
""width"": 300,
""height"": 200
}}
]
}}";

var requestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Post, $"{Customer}/collections", JsonSerializer.Serialize(collection));
var requestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Post, $"{Customer}/collections", collection);

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -128,13 +140,14 @@ await amazonS3.GetObjectAsync(LocalStackFixture.StorageBucketName,
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
fromDatabase.Parent.Should().Be(parent);
fromDatabase.Label!.Values.First()[0].Should().Be("test collection");
fromDatabase.Label!.Values.First()[0].Should().Be("iiif post");
fromDatabase.Slug.Should().Be("iiif-child");
fromDatabase.ItemsOrder.Should().Be(1);
fromDatabase.Tags.Should().Be("some, tags");
fromDatabase.IsPublic.Should().BeTrue();
fromDatabase.IsStorageCollection.Should().BeFalse();
fromDatabase.Modified.Should().Be(fromDatabase.Created);
fromDatabase.Thumbnail.Should().Be("https://example.org/img/thumb.jpg");
responseCollection!.View!.PageSize.Should().Be(20);
responseCollection.View.Page.Should().Be(1);
responseCollection.View.Id.Should().Contain("?page=1&pageSize=20");
Expand All @@ -155,7 +168,7 @@ public async Task CreateCollection_ReturnsError_WhenIsStorageCollectionFalseAndU
Slug = "iiif-child",
Parent = parent,
Tags = "some, tags",
Thumbnail = "some/thumbnail",
PresentationThumbnail = "some/thumbnail",
ItemsOrder = 1,
};

Expand All @@ -168,7 +181,7 @@ public async Task CreateCollection_ReturnsError_WhenIsStorageCollectionFalseAndU

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
error!.Detail.Should().Be("Error attempting to validate collection is IIIF");
error!.Detail.Should().Be("An error occurred while attempting to validate the collection as IIIF");
}

[Fact]
Expand Down Expand Up @@ -412,7 +425,7 @@ public async Task UpdateCollection_UpdatesCollection_WhenAllValuesProvided()
Slug = "programmatic-child",
Parent = parent,
ItemsOrder = 1,
Thumbnail = "some/location/2",
PresentationThumbnail = "some/location/2",
Tags = "some, tags, 2",
};

Expand Down Expand Up @@ -457,7 +470,7 @@ public async Task UpdateCollection_CreatesCollection_WhenUnknownCollectionIdProv
Slug = "create-from-update",
Parent = parent,
ItemsOrder = 1,
Thumbnail = "some/location/2",
PresentationThumbnail = "some/location/2",
Tags = "some, tags, 2",
};

Expand Down Expand Up @@ -502,7 +515,7 @@ public async Task UpdateCollection_FailsToCreateCollection_WhenUnknownCollection
Slug = "create-from-update-2",
Parent = parent,
ItemsOrder = 1,
Thumbnail = "some/location/2",
PresentationThumbnail = "some/location/2",
Tags = "some, tags, 2",
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static ModifyEntityResult<TCollection, ModifyCollectionType> CannotValida
where TCollection : class
{
return ModifyEntityResult<TCollection, ModifyCollectionType>.Failure(
"Error attempting to validate collection is IIIF", ModifyCollectionType.CannotValidateIIIF,
WriteResult.BadRequest);
"An error occurred while attempting to validate the collection as IIIF",
ModifyCollectionType.CannotValidateIIIF, WriteResult.BadRequest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
using AWS.S3.Models;
using Core;
using Core.Helpers;
using IIIF.Presentation.V3.Content;
using IIIF.Serialisation;
using MediatR;
using Microsoft.Extensions.Options;
using Models.API.Collection;
using Models.API.Collection.Upsert;
using Models.API.General;
using Models.Database.Collections;
using Newtonsoft.Json;
using NuGet.Protocol;
using Repository;
using Repository.Helpers;
using IIdGenerator = API.Infrastructure.IdGenerator.IIdGenerator;
Expand Down Expand Up @@ -80,13 +80,28 @@ public async Task<ModifyEntityResult<PresentationCollection, ModifyCollectionTyp
Label = request.Collection.Label,
Parent = parentCollection.Id,
Slug = request.Collection.Slug,
Thumbnail = request.Collection.Thumbnail,
Tags = request.Collection.Tags,
ItemsOrder = request.Collection.ItemsOrder
};

await using var transaction =
await dbContext.Database.BeginTransactionAsync(cancellationToken);
string? convertedIIIFCollection = null;

if (!request.Collection.Behavior.IsStorageCollection())
{
try
{
convertedIIIFCollection = ConvertToIIIFCollection(request, collection);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while attempting to validate the collection as IIIF");
return ErrorHelper.CannotValidateIIIF<PresentationCollection>();
}
}
else
{
collection.Thumbnail = request.Collection.PresentationThumbnail;
}

dbContext.Collections.Add(collection);

Expand All @@ -99,34 +114,7 @@ await dbContext.TrySaveCollection<PresentationCollection>(request.CustomerId, lo
return saveErrors;
}

if (!request.Collection.Behavior.IsStorageCollection())
{
try
{
var collectionToSave = GenerateCollectionFromRawRequest(request.RawRequestBody);

await bucketWriter.WriteToBucket(
new ObjectInBucket(settings.AWS.S3.StorageBucket,
$"{request.CustomerId}/collections/{collection.Id}"),
collectionToSave, "application/json", cancellationToken);
}
catch (JsonSerializationException ex)
{
logger.LogError(ex, "Error attempting to validate collection is IIIF");
return ModifyEntityResult<PresentationCollection, ModifyCollectionType>.Failure(
"Error attempting to validate collection is IIIF", ModifyCollectionType.CannotValidateIIIF,
WriteResult.BadRequest);
}
catch (Exception ex)
{
logger.LogError(ex, "An unknown exception occured while creating a new collection");
return ModifyEntityResult<PresentationCollection, ModifyCollectionType>.Failure(
"Unknown error occured while creating a collection", ModifyCollectionType.Unknown,
WriteResult.Error);
}
}

await transaction.CommitAsync(cancellationToken);
await UploadToS3IfRequiredAsync(request.Collection, collection, convertedIIIFCollection!, cancellationToken);

if (collection.Parent != null)
{
Expand All @@ -138,9 +126,27 @@ await bucketWriter.WriteToBucket(
WriteResult.Created);
}

private static string GenerateCollectionFromRawRequest(string rawRequestBody)
private static string ConvertToIIIFCollection(CreateCollection request, Collection collection)
{
var collectionAsIIIF = request.RawRequestBody.FromJson<IIIF.Presentation.V3.Collection>();
var convertedIIIFCollection = collectionAsIIIF.AsJson();
var thumbnails = collectionAsIIIF.Thumbnail?.OfType<Image>().ToList();
if (thumbnails != null)
{
collection.Thumbnail = thumbnails.GetThumbnailPath();
}
return convertedIIIFCollection;
}

private async Task UploadToS3IfRequiredAsync(UpsertFlatCollection request,
Collection collection, string convertedIIIFCollection, CancellationToken cancellationToken)
{
var collection = rawRequestBody.FromJson<IIIF.Presentation.V3.Collection>();
return collection.ToJson();
if (!request.Behavior.IsStorageCollection())
{
await bucketWriter.WriteToBucket(
new ObjectInBucket(settings.AWS.S3.StorageBucket,
collection.GetCollectionBucketKey()),
convertedIIIFCollection, "application/json", cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public async Task<CollectionWithItems> Handle(GetHierarchicalCollection request,
if (!collection.IsStorageCollection)
{
var objectFromS3 = await bucketReader.GetObjectFromBucket(new ObjectInBucket(settings.S3.StorageBucket,
$"{request.CustomerId}/collections/{collection.Id}"), cancellationToken);
collection.GetCollectionBucketKey()), cancellationToken);

if (!objectFromS3.Stream.IsNull())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ await dbContext.TrySaveCollection<Collection>(request.CustomerId, logger,

await bucketWriter.WriteToBucket(
new ObjectInBucket(settings.AWS.S3.StorageBucket,
$"{request.CustomerId}/collections/{collection.Id}"),
collection.GetCollectionBucketKey()),
collectionFromBody.AsJson(), "application/json", cancellationToken);

if (collection.Parent != null)
Expand Down Expand Up @@ -120,7 +120,7 @@ private static DatabaseCollection.Collection CreateDatabaseCollection(PostHierar
}
catch (Exception ex)
{
logger.LogError(ex, "Error attempting to validate collection is IIIF");
logger.LogError(ex, "An error occurred while attempting to validate the collection as IIIF");
}

return collection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public async Task<ModifyEntityResult<PresentationCollection, ModifyCollectionTyp
Label = request.Collection.Label,
Parent = parentCollection.Id,
Slug = request.Collection.Slug,
Thumbnail = request.Collection.Thumbnail,
Thumbnail = request.Collection.PresentationThumbnail,
Tags = request.Collection.Tags,
ItemsOrder = request.Collection.ItemsOrder
};
Expand Down Expand Up @@ -116,7 +116,7 @@ public async Task<ModifyEntityResult<PresentationCollection, ModifyCollectionTyp
databaseCollection.Label = request.Collection.Label;
databaseCollection.Parent = request.Collection.Parent;
databaseCollection.Slug = request.Collection.Slug;
databaseCollection.Thumbnail = request.Collection.Thumbnail;
databaseCollection.Thumbnail = request.Collection.PresentationThumbnail;
databaseCollection.Tags = request.Collection.Tags;
databaseCollection.ItemsOrder = request.Collection.ItemsOrder;
}
Expand Down
14 changes: 10 additions & 4 deletions src/IIIFPresentation/API/Features/Storage/StorageController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

namespace API.Features.Storage;

[Route("/{customerId}")]
[Route("/{customerId:int}")]
[ApiController]
public class StorageController(IAuthenticator authenticator, IOptions<ApiSettings> options, IMediator mediator)
: PresentationController(options.Value, mediator)
Expand Down Expand Up @@ -102,15 +102,21 @@ public async Task<IActionResult> Post(int customerId, [FromServices] UpsertFlatC
var rawRequestBody = await streamReader.ReadToEndAsync();

var collection = JsonConvert.DeserializeObject<UpsertFlatCollection>(rawRequestBody);

var validation = await validator.ValidateAsync(collection!);

if (collection == null)
{
return this.PresentationProblem("Could not deserialize collection", null, (int)HttpStatusCode.BadRequest,
"Deserialization Error");
}

var validation = await validator.ValidateAsync(collection);

if (!validation.IsValid)
{
return this.ValidationFailed(validation);
}

return await HandleUpsert(new CreateCollection(customerId, collection!, rawRequestBody, GetUrlRoots()));
return await HandleUpsert(new CreateCollection(customerId, collection, rawRequestBody, GetUrlRoots()));
}

[Authorize]
Expand Down
3 changes: 3 additions & 0 deletions src/IIIFPresentation/API/Helpers/CollectionHelperX.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public static Uri GenerateFlatCollectionViewLast(this Collection collection, Url
new(
$"{collection.GenerateFlatCollectionId(urlRoots)}?page={lastPage}&pageSize={pageSize}{orderQueryParam}");

public static string GetCollectionBucketKey(this Collection collection) =>
$"{collection.CustomerId}/collections/{collection.Id}";

public static string GenerateFullPath(this Collection collection, string itemSlug) =>
$"{(collection.Parent != null ? $"{collection.Slug}/" : string.Empty)}{itemSlug}";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using IIIF.Presentation.V3.Strings;
using System.Diagnostics;
using IIIF.Presentation.V3.Content;
using IIIF.Presentation.V3.Strings;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Models.API.Collection.Upsert;

Expand All @@ -14,7 +18,19 @@ public class UpsertFlatCollection

public string? Tags { get; set; }

public string? Thumbnail { get; set; }
public string? PresentationThumbnail { get; set; }

[JsonProperty("thumbnail")]
public object? ThumbnailFromRequest
{
set
{
if (value is not JArray)
{
PresentationThumbnail = value?.ToString();
}
}
}

public int? ItemsOrder { get; set; }
}

0 comments on commit b750993

Please sign in to comment.