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

Add asset metadata table for storing thumbnail sizes in the database #801

Merged
merged 11 commits into from
Apr 15, 2024
6 changes: 6 additions & 0 deletions src/protagonist/DLCS.Model/Assets/Asset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using DLCS.Core.Collections;
using DLCS.Core.Types;
using DLCS.Model.Assets.Metadata;

namespace DLCS.Model.Assets;

Expand Down Expand Up @@ -99,6 +100,11 @@ public IEnumerable<string> TagsList
/// A list of image delivery channels attached to this asset
/// </summary>
public ICollection<ImageDeliveryChannel> ImageDeliveryChannels { get; set; }

/// <summary>
/// A list of metadata attached to this asset
/// </summary>
public ICollection<AssetApplicationMetadata> AssetApplicationMetadata { get; set; }

public Asset()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using DLCS.Core.Types;

namespace DLCS.Model.Assets.Metadata;

public class AssetApplicationMetadata
{
/// <summary>
/// The image id for the attached asset
/// </summary>
public AssetId AssetId { get; set; }

public Asset Asset { get; set; }

/// <summary>
/// Identifier for the type of metadata
/// </summary>
public string MetadataType { get; set; }

/// <summary>
/// JSON object of values for type
/// </summary>
public string MetadataValue { get; set; }

/// <summary>
/// When the metadata was created
/// </summary>
public DateTime Created { get; set; }

/// <summary>
/// When the metadata was last modified
/// </summary>
public DateTime Modified { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DLCS.Model.Assets.Metadata;

public static class AssetApplicationMetadataTypes
{
public const string ThumbSizes = "ThumbSizes";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Threading;
using System.Threading.Tasks;
using DLCS.Core.Types;

namespace DLCS.Model.Assets.Metadata;

public interface IAssetApplicationMetadataRepository
{
/// <summary>
/// Upserts asset application metadata into the table
/// </summary>
/// <param name="assetId">The asset id this metadata will relate to</param>
/// <param name="metadataType">The type of metadata to create</param>
/// <param name="metadataValue">The value of this metadata</param>
/// <param name="cancellationToken">A cancellation token</param>
/// <returns>A copy of the metadata that has been put into the database</returns>
public Task<AssetApplicationMetadata> UpsertApplicationMetadata(
AssetId assetId, string metadataType, string metadataValue,
CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using DLCS.Core.Types;
using DLCS.Model.Assets.Metadata;
using DLCS.Repository.Assets;
using Microsoft.EntityFrameworkCore;
using Test.Helpers.Integration;

namespace DLCS.Repository.Tests.Assets;

[Trait("Category", "Database")]
[Collection(DatabaseCollection.CollectionName)]
public class AssetApplicationMetadataRepositoryTests
{
private readonly DlcsContext dbContext;
private readonly AssetApplicationMetadataRepository sut;
private readonly DlcsContext contextForTests;

public AssetApplicationMetadataRepositoryTests(DlcsDatabaseFixture dbFixture)
{
dbContext = dbFixture.DbContext;

var optionsBuilder = new DbContextOptionsBuilder<DlcsContext>();
optionsBuilder.UseNpgsql(dbFixture.ConnectionString);
contextForTests = new DlcsContext(optionsBuilder.Options);

sut = new AssetApplicationMetadataRepository(contextForTests);

dbFixture.CleanUp();
dbContext.Images.AddTestAsset(AssetId.FromString("99/1/1"), ref1: "foobar");
dbContext.SaveChanges();
}

[Fact]
public async Task UpsertApplicationMetadata_AddsMetadata_WhenCalledWithNew()
{
// Arrange
var assetId = AssetId.FromString("99/1/1");
var metadataValue = "{\"a\": [], \"o\": [[75, 100], [150, 200], [300, 400], [769, 1024]]}";

// Act
var metadata = await sut.UpsertApplicationMetadata(assetId,
AssetApplicationMetadataTypes.ThumbSizes, metadataValue);

var metaDataFromDatabase = await dbContext.AssetApplicationMetadata.FirstAsync(x =>
x.AssetId == assetId && x.MetadataType == AssetApplicationMetadataTypes.ThumbSizes);

// Assert
metadata.Should().NotBeNull();
metaDataFromDatabase.Should().NotBeNull();
metaDataFromDatabase.MetadataValue.Should().Be(metadataValue);
}

[Fact]
public async Task UpsertApplicationMetadata_UpdatesMetadata_WhenCalledWithUpdated()
{
// Arrange
var assetId = AssetId.FromString("99/1/1");
await sut.UpsertApplicationMetadata(assetId,
AssetApplicationMetadataTypes.ThumbSizes, "{\"a\": [], \"o\": []}");
var newMetadataValue = "{\"a\": [], \"o\": [[75, 100], [150, 200], [300, 400], [769, 1024]]}";

// Act
var metadata = await sut.UpsertApplicationMetadata(assetId,
AssetApplicationMetadataTypes.ThumbSizes, newMetadataValue);
var metaDataFromDatabase = await contextForTests.AssetApplicationMetadata.FirstAsync(x =>
x.AssetId == assetId && x.MetadataType == AssetApplicationMetadataTypes.ThumbSizes);

// Assert
metadata.Should().NotBeNull();
metaDataFromDatabase.Should().NotBeNull();
metaDataFromDatabase.MetadataValue.Should().Be(newMetadataValue);
}

[Fact]
public async Task UpsertApplicationMetadata_ThrowsException_WhenCalledWithInvalidJson()
{
// Arrange
var assetId = AssetId.FromString("99/1/1");
var metadataValue = "not json";

// Act
Func<Task> action = async () => await sut.UpsertApplicationMetadata(assetId,
AssetApplicationMetadataTypes.ThumbSizes, metadataValue);

// Assert
await action.Should().ThrowAsync<DbUpdateException>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using DLCS.Core.Types;
using DLCS.Model.Assets.Metadata;
using Microsoft.EntityFrameworkCore;

namespace DLCS.Repository.Assets;

public class AssetApplicationMetadataRepository : IAssetApplicationMetadataRepository
{
private readonly DlcsContext dlcsContext;

public AssetApplicationMetadataRepository(DlcsContext dlcsContext)
{
this.dlcsContext = dlcsContext;
}

public async Task<AssetApplicationMetadata> UpsertApplicationMetadata(AssetId assetId, string metadataType, string metadataValue,
CancellationToken cancellationToken = default)
{
var addedMetadata = await dlcsContext.AssetApplicationMetadata.FirstOrDefaultAsync(e =>
e.AssetId == assetId && e.MetadataType == metadataType, cancellationToken);

if (addedMetadata is not null)
{
addedMetadata.MetadataValue = metadataValue;
addedMetadata.Modified = DateTime.UtcNow;
await dlcsContext.SaveChangesAsync(cancellationToken);
return addedMetadata;
}

var assetApplicationMetadata = new AssetApplicationMetadata()
{
AssetId = assetId,
MetadataType = metadataType,
MetadataValue = metadataValue,
Created = DateTime.UtcNow,
Modified = DateTime.UtcNow
};

await dlcsContext.AssetApplicationMetadata.AddAsync(assetApplicationMetadata, cancellationToken);

await dlcsContext.SaveChangesAsync(cancellationToken);
return assetApplicationMetadata;
}
}
11 changes: 9 additions & 2 deletions src/protagonist/DLCS.Repository/Assets/Thumbs/ThumbsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using DLCS.AWS.S3;
using DLCS.Core.Types;
using DLCS.Model.Assets;
using DLCS.Model.Assets.Metadata;
using DLCS.Model.Policies;
using IIIF;
using Newtonsoft.Json;
Expand All @@ -15,14 +16,17 @@ public abstract class ThumbsManager
{
protected readonly IBucketWriter BucketWriter;
protected readonly IStorageKeyGenerator StorageKeyGenerator;
protected readonly IAssetApplicationMetadataRepository AssetApplicationMetadataRepository;

public ThumbsManager(
IBucketWriter bucketWriter,
IStorageKeyGenerator storageKeyGenerator
IStorageKeyGenerator storageKeyGenerator,
IAssetApplicationMetadataRepository assetApplicationMetadataRepository
)
{
BucketWriter = bucketWriter;
StorageKeyGenerator = storageKeyGenerator;
AssetApplicationMetadataRepository = assetApplicationMetadataRepository;
}

protected static Size GetMaxAvailableThumb(Asset asset, ThumbnailPolicy policy)
Expand All @@ -33,8 +37,11 @@ protected static Size GetMaxAvailableThumb(Asset asset, ThumbnailPolicy policy)

protected async Task CreateSizesJson(AssetId assetId, ThumbnailSizes thumbnailSizes)
{
var serializedThumbnailSizes = JsonConvert.SerializeObject(thumbnailSizes);
var sizesDest = StorageKeyGenerator.GetThumbsSizesJsonLocation(assetId);
await BucketWriter.WriteToBucket(sizesDest, JsonConvert.SerializeObject(thumbnailSizes),
await BucketWriter.WriteToBucket(sizesDest, serializedThumbnailSizes,
"application/json");
await AssetApplicationMetadataRepository.UpsertApplicationMetadata(assetId,
AssetApplicationMetadataTypes.ThumbSizes, serializedThumbnailSizes);
}
}
16 changes: 16 additions & 0 deletions src/protagonist/DLCS.Repository/DlcsContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using DLCS.Core.Types;
using DLCS.Model.Assets;
using DLCS.Model.Assets.CustomHeaders;
using DLCS.Model.Assets.Metadata;
using DLCS.Model.Assets.NamedQueries;
using DLCS.Model.Auth.Entities;
using DLCS.Model.Customers;
Expand Down Expand Up @@ -77,6 +78,7 @@ public DlcsContext(DbContextOptions<DlcsContext> options)
public virtual DbSet<DeliveryChannelPolicy> DeliveryChannelPolicies { get; set; }
public virtual DbSet<ImageDeliveryChannel> ImageDeliveryChannels { get; set; }
public virtual DbSet<DefaultDeliveryChannel> DefaultDeliveryChannels { get; set; }
public virtual DbSet<AssetApplicationMetadata> AssetApplicationMetadata { get; set; }

public virtual DbSet<SignupLink> SignupLinks { get; set; }

Expand Down Expand Up @@ -680,6 +682,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
entity.Property(e => e.MediaType).IsRequired().HasMaxLength(255);
});

modelBuilder.Entity<AssetApplicationMetadata>(entity =>
{
entity.HasKey(e => new { e.AssetId, e.MetadataType });
entity.Property(e => e.AssetId).IsRequired().HasConversion(
aId => aId.ToString(),
id => AssetId.FromString(id));
entity.Property(e => e.MetadataType).IsRequired();
entity.Property(e => e.MetadataValue).IsRequired().HasColumnType("jsonb");

entity.HasOne(e => e.Asset)
.WithMany(e => e.AssetApplicationMetadata)
.HasForeignKey(e => e.AssetId);
});

OnModelCreatingPartial(modelBuilder);
}

Expand Down
Loading
Loading