Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update collections to only refer to the root collection as /root #33

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/IIIFPresentation/API.Tests/Integration/GetCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public async Task Get_RootFlat_ReturnsEntryPointFlat_WhenAuthAndHeader()

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
collection!.Id.Should().Be("http://localhost/1/collections/RootStorage");
collection!.Id.Should().Be("http://localhost/1/collections/root");
collection.PublicId.Should().Be("http://localhost/1");
collection.Items!.Count.Should().Be(2);
collection.Items[0].Id.Should().Be("http://localhost/1/collections/FirstChildCollection");
Expand All @@ -162,13 +162,14 @@ public async Task Get_RootFlat_ReturnsEntryPointFlat_WhenCalledById()

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
collection!.Id.Should().Be("http://localhost/1/collections/RootStorage");
collection!.Id.Should().Be("http://localhost/1/collections/root");
collection.PublicId.Should().Be("http://localhost/1");
collection.Items!.Count.Should().Be(2);
collection.Items[0].Id.Should().Be("http://localhost/1/collections/FirstChildCollection");
collection.TotalItems.Should().Be(2);
collection.CreatedBy.Should().Be("admin");
collection.Behavior.Should().Contain("public-iiif");
collection.Parent.Should().BeNull();
}

[Fact]
Expand All @@ -192,6 +193,7 @@ public async Task Get_ChildFlat_ReturnsEntryPointFlat_WhenCalledByChildId()
collection.TotalItems.Should().Be(1);
collection.CreatedBy.Should().Be("admin");
collection.Behavior.Should().Contain("public-iiif");
collection.Parent.Should().Be("http://localhost/1/collections/root");
}

[Fact]
Expand All @@ -214,6 +216,7 @@ public async Task Get_PrivateChild_ReturnsCorrectlyFlatAndHierarchical()
flatCollection.CreatedBy.Should().Be("admin");
flatCollection.Behavior.Should().Contain("storage-collection");
flatCollection.Behavior.Should().NotContain("public-iiif");
flatCollection.Parent.Should().Be("http://localhost/1/collections/root");
hierarchicalResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
}

Expand All @@ -238,7 +241,7 @@ public async Task Get_RootFlat_ReturnsItems_WhenCalledWithPageSize()
}

[Fact]
public async Task Get_RootFlat_ReturnsReducedItems_WhenCalledWithSmallPageSize()
public async Task Get_ChildFlat_ReturnsReducedItems_WhenCalledWithSmallPageSize()
{
// Arrange
var requestMessage =
Expand Down Expand Up @@ -323,7 +326,7 @@ public async Task Get_RootFlat_ReturnsMaxPageSize_WhenCalledPageSizeExceedsMax()
[InlineData("id")]
[InlineData("slug")]
[InlineData("created")]
public async Task Get_RootFlat_ReturnsCorrectItem_WhenCalledWithSmallPageSizeAndOrderBy(string field)
public async Task Get_ChildFlat_ReturnsCorrectItem_WhenCalledWithSmallPageSizeAndOrderBy(string field)
{
// Arrange
var requestMessage =
Expand Down Expand Up @@ -363,15 +366,15 @@ public async Task Get_RootFlat_ReturnsFirstPageWithSecondItem_WhenCalledWithSmal
// Assert
collection.TotalItems.Should().Be(2);
collection.View!.PageSize.Should().Be(1);
collection.View.Id.Should().Be($"http://localhost/1/collections/RootStorage?page=1&pageSize=1&orderByDescending={field}");
collection.View.Id.Should().Be($"http://localhost/1/collections/root?page=1&pageSize=1&orderByDescending={field}");
collection.View.Page.Should().Be(1);
collection.View.TotalPages.Should().Be(2);
collection.Items!.Count.Should().Be(1);
collection.Items[0].Id.Should().Be("http://localhost/1/collections/NonPublic");
}

[Fact]
public async Task Get_RootFlat_IgnoresOrderBy_WhenCalledWithInvalidOrderBy()
public async Task Get_ChildFlat_IgnoresOrderBy_WhenCalledWithInvalidOrderBy()
{
// Arrange
var requestMessage =
Expand All @@ -386,7 +389,7 @@ public async Task Get_RootFlat_IgnoresOrderBy_WhenCalledWithInvalidOrderBy()
// Assert
collection.TotalItems.Should().Be(2);
collection.View!.PageSize.Should().Be(1);
collection.View.Id.Should().Be($"http://localhost/1/collections/RootStorage?page=1&pageSize=1");
collection.View.Id.Should().Be($"http://localhost/1/collections/root?page=1&pageSize=1");
collection.View.Page.Should().Be(1);
collection.View.TotalPages.Should().Be(2);
collection.Items!.Count.Should().Be(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ public async Task CreateCollection_CreatesCollection_WhenAllValuesProvided()
fromDatabase.Tags.Should().Be("some, tags");
fromDatabase.IsPublic.Should().BeTrue();
fromDatabase.IsStorageCollection.Should().BeTrue();
responseCollection!.View!.PageSize.Should().Be(20);
responseCollection.View.Page.Should().Be(1);
responseCollection.View.Id.Should().Contain("?page=1&pageSize=20");
}

[Fact]
Expand Down Expand Up @@ -283,6 +286,9 @@ public async Task UpdateCollection_UpdatesCollection_WhenAllValuesProvided()
fromDatabase.Tags.Should().Be("some, tags, 2");
fromDatabase.IsPublic.Should().BeTrue();
fromDatabase.IsStorageCollection.Should().BeTrue();
responseCollection!.View!.PageSize.Should().Be(20);
responseCollection.View.Page.Should().Be(1);
responseCollection.View.Id.Should().Contain("?page=1&pageSize=20");
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion src/IIIFPresentation/API/Converters/CollectionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@
{
var view = new View()
{
Id = dbAsset.GenerateFlatCollectionViewId(urlRoots, currentPage, pageSize, orderQueryParam),

Check warning on line 116 in src/IIIFPresentation/API/Converters/CollectionConverter.cs

View workflow job for this annotation

GitHub Actions / test-dotnet

Possible null reference argument for parameter 'orderQueryParam' in 'string CollectionHelperX.GenerateFlatCollectionViewId(Collection collection, UrlRoots urlRoots, int currentPage, int pageSize, string orderQueryParam)'.

Check warning on line 116 in src/IIIFPresentation/API/Converters/CollectionConverter.cs

View workflow job for this annotation

GitHub Actions / test-dotnet

Possible null reference argument for parameter 'orderQueryParam' in 'string CollectionHelperX.GenerateFlatCollectionViewId(Collection collection, UrlRoots urlRoots, int currentPage, int pageSize, string orderQueryParam)'.

Check warning on line 116 in src/IIIFPresentation/API/Converters/CollectionConverter.cs

View workflow job for this annotation

GitHub Actions / test-dotnet

Possible null reference argument for parameter 'orderQueryParam' in 'string CollectionHelperX.GenerateFlatCollectionViewId(Collection collection, UrlRoots urlRoots, int currentPage, int pageSize, string orderQueryParam)'.

Check warning on line 116 in src/IIIFPresentation/API/Converters/CollectionConverter.cs

View workflow job for this annotation

GitHub Actions / test-dotnet

Possible null reference argument for parameter 'orderQueryParam' in 'string CollectionHelperX.GenerateFlatCollectionViewId(Collection collection, UrlRoots urlRoots, int currentPage, int pageSize, string orderQueryParam)'.
Type = PresentationType.PartialCollectionView,
Page = currentPage,
PageSize = pageSize,
TotalPages = totalPages,
};

if (totalPages > 1)
if (currentPage > 1)
{
view.First = dbAsset.GenerateFlatCollectionViewFirst(urlRoots, pageSize, orderQueryParam);
view.Previous = dbAsset.GenerateFlatCollectionViewPrevious(urlRoots, currentPage, pageSize, orderQueryParam);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Core;
using Microsoft.EntityFrameworkCore;
using Models.API.Collection;
using Models.Database.Collections;
using Repository;
using Repository.Helpers;

Expand Down Expand Up @@ -35,4 +36,24 @@ public static class PresentationContextX

return null;
}

public static async Task<Collection?> RetrieveCollection(this PresentationContext dbContext, int customerId, string collectionId,
string rootCollectionId, CancellationToken cancellationToken)
{
Collection? collection;
if (collectionId.Equals(rootCollectionId, StringComparison.OrdinalIgnoreCase))
{
collection = await dbContext.Collections.AsNoTracking().FirstOrDefaultAsync(
s => s.CustomerId == customerId && s.Parent == null,
cancellationToken);
}
else
{
collection = await dbContext.Collections.AsNoTracking().FirstOrDefaultAsync(
s => s.CustomerId == customerId && s.Id == collectionId,
cancellationToken);
}

return collection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace API.Features.Storage.Helpers;

public static class RootCollection
{
public const string Id = "root";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using API.Auth;
using API.Converters;
using API.Features.Storage.Helpers;
using API.Helpers;
using API.Infrastructure.Requests;
using API.Settings;
using Core;
Expand Down Expand Up @@ -33,8 +34,20 @@ public class CreateCollectionHandler(
{
private readonly ApiSettings settings = options.Value;

private const int CurrentPage = 1;

public async Task<ModifyEntityResult<FlatCollection>> Handle(CreateCollection request, CancellationToken cancellationToken)
{
// check parent exists
var parentCollection = await dbContext.RetrieveCollection(request.CustomerId,
request.Collection.Parent.GetLastPathElement(), RootCollection.Id, cancellationToken);

if (parentCollection == null)
{
return ModifyEntityResult<FlatCollection>.Failure(
$"The parent collection could not be found", WriteResult.Conflict);
}

var collection = new Collection()
{
Id = Guid.NewGuid().ToString(),
Expand All @@ -45,7 +58,7 @@ public async Task<ModifyEntityResult<FlatCollection>> Handle(CreateCollection re
IsPublic = request.Collection.Behavior.IsPublic(),
IsStorageCollection = request.Collection.Behavior.IsStorageCollection(),
Label = request.Collection.Label,
Parent = request.Collection.Parent!.GetLastPathElement(),
Parent = parentCollection.Id,
Slug = request.Collection.Slug,
Thumbnail = request.Collection.Thumbnail,
Tags = request.Collection.Tags,
Expand All @@ -66,8 +79,10 @@ public async Task<ModifyEntityResult<FlatCollection>> Handle(CreateCollection re
collection.FullPath = CollectionRetrieval.RetrieveFullPathForCollection(collection, dbContext);
}

collection.UpdateParentForRootIfRequired(request.Collection.Parent);

return ModifyEntityResult<FlatCollection>.Success(
collection.ToFlatCollection(request.UrlRoots, 1, settings.PageSize, 0, []), // there can be no items attached to this, as it's just been created
collection.ToFlatCollection(request.UrlRoots, settings.PageSize, CurrentPage, 0, []), // there can be no items attached to this, as it's just been created
WriteResult.Created);
}
}
32 changes: 15 additions & 17 deletions src/IIIFPresentation/API/Features/Storage/Requests/GetCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using API.Features.Storage.Models;
using API.Features.Storage.Helpers;
using API.Features.Storage.Models;
using API.Helpers;
using MediatR;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -28,25 +29,11 @@ public class GetCollection(

public class GetCollectionHandler(PresentationContext dbContext) : IRequestHandler<GetCollection, CollectionWithItems>
{
private const string RootCollection = "root";

public async Task<CollectionWithItems> Handle(GetCollection request,
CancellationToken cancellationToken)
{
Collection? collection;

if (request.Id.Equals(RootCollection, StringComparison.OrdinalIgnoreCase))
{
collection = await dbContext.Collections.AsNoTracking().FirstOrDefaultAsync(
s => s.CustomerId == request.CustomerId && s.Parent == null,
cancellationToken);
}
else
{
collection = await dbContext.Collections.AsNoTracking().FirstOrDefaultAsync(
s => s.CustomerId == request.CustomerId && s.Id == request.Id,
cancellationToken);
}
Collection? collection = await dbContext.RetrieveCollection(request.CustomerId, request.Id, RootCollection.Id,
cancellationToken);

List<Collection>? items = null;
int total = 0;
Expand All @@ -71,6 +58,17 @@ public async Task<CollectionWithItems> Handle(GetCollection request,
if (collection.Parent != null)
{
collection.FullPath = CollectionRetrieval.RetrieveFullPathForCollection(collection, dbContext);

var parentCollection = await dbContext.RetrieveCollection(request.CustomerId, collection.Parent, RootCollection.Id,
cancellationToken);
if (parentCollection!.Parent == null)
{
collection.Parent = RootCollection.Id;
}
}
else
{
collection.Id = RootCollection.Id;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using API.Auth;
using API.Converters;
using API.Features.Storage.Helpers;
using API.Helpers;
using API.Infrastructure.Requests;
using API.Settings;
using Core;
Expand Down Expand Up @@ -47,13 +48,22 @@ public async Task<ModifyEntityResult<FlatCollection>> Handle(UpdateCollection re
return ModifyEntityResult<FlatCollection>.Failure(
"Could not find a matching record for the provided collection id", WriteResult.NotFound);
}

var parentCollection = await dbContext.RetrieveCollection(request.CustomerId,
request.Collection.Parent.GetLastPathElement(), RootCollection.Id, cancellationToken);

if (parentCollection == null)
{
return ModifyEntityResult<FlatCollection>.Failure(
$"The parent collection could not be found", WriteResult.Conflict);
}

collectionFromDatabase.Modified = DateTime.UtcNow;
collectionFromDatabase.ModifiedBy = Authorizer.GetUser();
collectionFromDatabase.IsPublic = request.Collection.Behavior.IsPublic();
collectionFromDatabase.IsStorageCollection = request.Collection.Behavior.IsStorageCollection();
collectionFromDatabase.Label = request.Collection.Label;
collectionFromDatabase.Parent = request.Collection.Parent.GetLastPathElement();
collectionFromDatabase.Parent = parentCollection.Id;
collectionFromDatabase.Slug = request.Collection.Slug;
collectionFromDatabase.Thumbnail = request.Collection.Thumbnail;
collectionFromDatabase.Tags = request.Collection.Tags;
Expand Down Expand Up @@ -85,6 +95,8 @@ public async Task<ModifyEntityResult<FlatCollection>> Handle(UpdateCollection re
CollectionRetrieval.RetrieveFullPathForCollection(collectionFromDatabase, dbContext);
}

collectionFromDatabase.UpdateParentForRootIfRequired(request.Collection.Parent);

return ModifyEntityResult<FlatCollection>.Success(
collectionFromDatabase.ToFlatCollection(request.UrlRoots, settings.PageSize, DefaultCurrentPage, total,
await items.ToListAsync(cancellationToken: cancellationToken)));
Expand Down
13 changes: 13 additions & 0 deletions src/IIIFPresentation/API/Helpers/CollectionHelperX.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using API.Converters;
using API.Features.Storage.Helpers;
using Core.Helpers;
using Models.Database.Collections;

namespace API.Helpers;
Expand Down Expand Up @@ -43,4 +45,15 @@ public static Uri GenerateFlatCollectionViewLast(this Collection collection, Url

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

public static Collection UpdateParentForRootIfRequired(this Collection collection, string parentToChangeTo)
{
// everything saved so set the response value to be the root collection if required
if (parentToChangeTo.GetLastPathElement() == RootCollection.Id)
{
collection.Parent = RootCollection.Id;
}

return collection;
}
}
Loading