From cbec168e79833ee1a134c2846aa44f682a5574ca Mon Sep 17 00:00:00 2001 From: Donald Gray Date: Tue, 30 Apr 2024 17:45:24 +0100 Subject: [PATCH] Presrvation id generation --- .../Fedora/Abstractions/Resource.cs | 2 +- .../Controllers/RepositoryController.cs | 24 +++-- .../Preservation.API/ModelConverters.cs | 93 ------------------- .../Preservation.API/Models/ModelConverter.cs | 88 ++++++++++++++++++ .../Preservation.API/{ => Models}/Models.cs | 2 +- .../Preservation.API/Models/UriGenerator.cs | 36 +++++++ LeedsExperiment/Preservation.API/Program.cs | 11 ++- LeedsExperiment/Preservation/FedoraWrapper.cs | 10 +- 8 files changed, 154 insertions(+), 112 deletions(-) delete mode 100644 LeedsExperiment/Preservation.API/ModelConverters.cs create mode 100644 LeedsExperiment/Preservation.API/Models/ModelConverter.cs rename LeedsExperiment/Preservation.API/{ => Models}/Models.cs (97%) create mode 100644 LeedsExperiment/Preservation.API/Models/UriGenerator.cs diff --git a/LeedsExperiment/Fedora/Abstractions/Resource.cs b/LeedsExperiment/Fedora/Abstractions/Resource.cs index a57684a..842e0de 100644 --- a/LeedsExperiment/Fedora/Abstractions/Resource.cs +++ b/LeedsExperiment/Fedora/Abstractions/Resource.cs @@ -22,7 +22,7 @@ protected Resource() [JsonPropertyName("@id")] [JsonPropertyOrder(1)] // The URI for this Storage API (accessible to API consumers) - public Uri? PreservationApiUri { get; set; } + public Uri? StorageApiUri { get; set; } [JsonPropertyName("id")] [JsonPropertyOrder(12)] diff --git a/LeedsExperiment/Preservation.API/Controllers/RepositoryController.cs b/LeedsExperiment/Preservation.API/Controllers/RepositoryController.cs index 7891166..07e36b3 100644 --- a/LeedsExperiment/Preservation.API/Controllers/RepositoryController.cs +++ b/LeedsExperiment/Preservation.API/Controllers/RepositoryController.cs @@ -1,28 +1,34 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using Preservation.API.Models; namespace Preservation.API.Controllers; [Route("[controller]/{*path}")] [ApiController] -public class RepositoryController(IPreservation preservation) : Controller +public class RepositoryController(IPreservation preservation, ModelConverter modelConverter) : Controller { /// /// Browse underlying repository for Container, DigitalObject or Binary. /// - /// - [HttpGet] + /// Path to item in repository to fetch (e.g. path/to/item + /// + /// Optional version parameter, in format "v1","v2" etc. Current version returned if omitted + /// + /// , or + [HttpGet(Name = "Browse")] + [Produces("application/json")] public async Task Index([FromRoute] string path, [FromQuery] string? version = null) { - // How do we know if this is an archive group or not? + var unEscapedPath = Uri.UnescapeDataString(path); var storageResource = string.IsNullOrEmpty(version) - ? await preservation.GetResource(path) - : await preservation.GetArchivalGroup(path, version); + ? await preservation.GetResource(unEscapedPath) + : await preservation.GetArchivalGroup(unEscapedPath, version); if (storageResource == null) return NotFound(); - - // Convert to appropriate type - var preservationResource = storageResource.ToPreservationResource(new Uri(HttpContext.Request.GetDisplayUrl())); + + var preservationResource = + modelConverter.ToPreservationResource(storageResource, new Uri(HttpContext.Request.GetDisplayUrl())); return Ok(preservationResource); } } \ No newline at end of file diff --git a/LeedsExperiment/Preservation.API/ModelConverters.cs b/LeedsExperiment/Preservation.API/ModelConverters.cs deleted file mode 100644 index a7a4e58..0000000 --- a/LeedsExperiment/Preservation.API/ModelConverters.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Fedora.Abstractions; -using Fedora.Storage; - -namespace Preservation.API; - -public static class ModelConverters -{ - public static PreservationResource ToPreservationResource(this Fedora.Abstractions.Resource storageResource, Uri requestPath) - { - switch (storageResource) - { - case Fedora.Abstractions.ArchivalGroup ag: - { - var digitalObject = new DigitalObject - { - Id = requestPath, // TODO remove version query param - Name = ag.Name, - Version = ag.Version.ToDigitalObjectVersion(requestPath), - Versions = (ag.Versions ?? Array.Empty()) - .Select(v => v.ToDigitalObjectVersion(requestPath)!).ToArray(), - Binaries = ag.Binaries.Select(b => b.ToPresentationBinary()).ToArray(), - Containers = ag.Containers.Select(c => c.ToPresentationContainer()).ToArray(), - }; - digitalObject.MapBasicsFromStorageResource(storageResource); - return digitalObject; - } - case Fedora.Abstractions.Container c: - return c.ToPresentationContainer(); - case Fedora.Abstractions.Binary b: - return b.ToPresentationBinary(); - } - - throw new InvalidOperationException($"Unable to handle {storageResource.GetType()} resource"); - } - - private static DigitalObjectVersion? ToDigitalObjectVersion(this ObjectVersion? objectVersion, Uri repositoryUri) - { - if (objectVersion == null) return null; - - var objectId = new UriBuilder(repositoryUri); - if (!string.IsNullOrEmpty(objectVersion.OcflVersion)) - { - objectId.Query = $"?version={objectVersion.OcflVersion}"; - } - - var digitalObjectVersion = new DigitalObjectVersion - { - Id = objectId.Uri, - Name = objectVersion.OcflVersion, - Date = objectVersion.MementoDateTime, - }; - return digitalObjectVersion; - } - - private static Binary ToPresentationBinary(this Fedora.Abstractions.Binary fedoraBinary) - { - var binary = new Binary - { - Id = new Uri("http://todo.uri"), - Content = new Uri("http://todo.uri"), - Name = fedoraBinary.FileName, - Digest = fedoraBinary.Digest, - Location = new Uri("s3://todo/path"), - PartOf = new Uri("http://todo.uri"), - }; - - binary.MapBasicsFromStorageResource(fedoraBinary); - return binary; - } - - private static Container ToPresentationContainer(this Fedora.Abstractions.Container fedoraContainer) - { - var container = new Container - { - Name = fedoraContainer.Name, - Containers = fedoraContainer.Containers.Select(c => c.ToPresentationContainer()).ToArray(), - Binaries = fedoraContainer.Binaries.Select(b => b.ToPresentationBinary()).ToArray(), - PartOf = new Uri("http://todo.uri"), - }; - - container.MapBasicsFromStorageResource(fedoraContainer); - return container; - } - - private static void MapBasicsFromStorageResource(this T target, Resource resource) - where T : PreservationResource, new() - { - target.Created = resource.Created ?? DateTime.MinValue; - target.CreatedBy = new Uri($"http://example.id/{resource.CreatedBy}"); - target.LastModified = resource.LastModified; - target.LastModifiedBy = new Uri($"http://example.id/{resource.LastModifiedBy}"); - } -} \ No newline at end of file diff --git a/LeedsExperiment/Preservation.API/Models/ModelConverter.cs b/LeedsExperiment/Preservation.API/Models/ModelConverter.cs new file mode 100644 index 0000000..d05ef73 --- /dev/null +++ b/LeedsExperiment/Preservation.API/Models/ModelConverter.cs @@ -0,0 +1,88 @@ +using Fedora.Abstractions; +using Fedora.Storage; + +namespace Preservation.API.Models; + +public class ModelConverter(UriGenerator uriGenerator) +{ + public PreservationResource ToPreservationResource(Fedora.Abstractions.Resource storageResource, Uri requestPath) + { + switch (storageResource) + { + case Fedora.Abstractions.ArchivalGroup ag: + { + var digitalObject = new DigitalObject + { + Id = requestPath, + Name = ag.Name, + Version = ToDigitalObjectVersion(ag.Version, requestPath), + Versions = (ag.Versions ?? Array.Empty()) + .Select(v => ToDigitalObjectVersion(v, requestPath)!).ToArray(), + Binaries = ag.Binaries.Select(b => ToPresentationBinary(b)).ToArray(), + Containers = ag.Containers.Select(c => ToPresentationContainer(c)).ToArray(), + }; + MapBasicsFromStorageResource(digitalObject, storageResource); + return digitalObject; + } + case Fedora.Abstractions.Container c: + return ToPresentationContainer(c); + case Fedora.Abstractions.Binary b: + return ToPresentationBinary(b); + } + + throw new InvalidOperationException($"Unable to handle {storageResource.GetType()} resource"); + } + + private DigitalObjectVersion? ToDigitalObjectVersion(ObjectVersion? objectVersion, Uri repositoryUri) + { + if (objectVersion == null) return null; + + var digitalObjectVersion = new DigitalObjectVersion + { + Id = uriGenerator.GetRepositoryPath(repositoryUri, objectVersion.OcflVersion), + Name = objectVersion.OcflVersion, + Date = objectVersion.MementoDateTime, + }; + return digitalObjectVersion; + } + + private Binary ToPresentationBinary(Fedora.Abstractions.Binary fedoraBinary) + { + var binary = new Binary + { + Id = uriGenerator.GetRepositoryPath(fedoraBinary.StorageApiUri), + Content = new Uri("https://todo"), + Name = fedoraBinary.FileName, + Digest = fedoraBinary.Digest, + Location = new Uri("s3://todo/path"), + PartOf = uriGenerator.GetRepositoryPath(fedoraBinary.PartOf), + }; + + MapBasicsFromStorageResource(binary, fedoraBinary); + return binary; + } + + private Container ToPresentationContainer(Fedora.Abstractions.Container fedoraContainer) + { + var container = new Container + { + Id = uriGenerator.GetRepositoryPath(fedoraContainer.StorageApiUri), + Name = fedoraContainer.Name, + Containers = fedoraContainer.Containers.Select(c => ToPresentationContainer(c)).ToArray(), + Binaries = fedoraContainer.Binaries.Select(b => ToPresentationBinary(b)).ToArray(), + PartOf = uriGenerator.GetRepositoryPath(fedoraContainer.PartOf), + }; + + MapBasicsFromStorageResource(container, fedoraContainer); + return container; + } + + private void MapBasicsFromStorageResource(T target, Resource resource) + where T : PreservationResource, new() + { + target.Created = resource.Created ?? DateTime.MinValue; + target.CreatedBy = new Uri($"http://example.id/{resource.CreatedBy}"); + target.LastModified = resource.LastModified; + target.LastModifiedBy = new Uri($"http://example.id/{resource.LastModifiedBy}"); + } +} \ No newline at end of file diff --git a/LeedsExperiment/Preservation.API/Models.cs b/LeedsExperiment/Preservation.API/Models/Models.cs similarity index 97% rename from LeedsExperiment/Preservation.API/Models.cs rename to LeedsExperiment/Preservation.API/Models/Models.cs index 2776d72..36825e9 100644 --- a/LeedsExperiment/Preservation.API/Models.cs +++ b/LeedsExperiment/Preservation.API/Models/Models.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Preservation.API; +namespace Preservation.API.Models; /// /// Base class for Preservation API models diff --git a/LeedsExperiment/Preservation.API/Models/UriGenerator.cs b/LeedsExperiment/Preservation.API/Models/UriGenerator.cs new file mode 100644 index 0000000..33ea1ae --- /dev/null +++ b/LeedsExperiment/Preservation.API/Models/UriGenerator.cs @@ -0,0 +1,36 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Preservation.API.Models; + +public class UriGenerator(IHttpContextAccessor httpContextAccessor) +{ + [return: NotNullIfNotNull(nameof(storageUri))] + public Uri? GetRepositoryPath(Uri? storageUri, string? version = null) + { + if (storageUri == null) return null; + + // Only append ?version if provided as arg, so use AbsolutePath only be default + var request = httpContextAccessor.HttpContext.Request; + var uriBuilder = new UriBuilder(request.Scheme, request.Host.Host) + { + Port = request.Host.Port ?? 80, + Path = GetPreservationPath(storageUri) + }; + + if (!string.IsNullOrEmpty(version)) + { + uriBuilder.Query = $"version={version}"; + } + + return uriBuilder.Uri; + } + + private static string GetPreservationPath(Uri storageUri) + { + var storageUriAbsolutePath = storageUri.AbsolutePath; + const string storageApiPrefix = "/api"; + return Uri.UnescapeDataString(storageUriAbsolutePath[..4] == storageApiPrefix + ? storageUriAbsolutePath[4..] + : storageUriAbsolutePath); + } +} \ No newline at end of file diff --git a/LeedsExperiment/Preservation.API/Program.cs b/LeedsExperiment/Preservation.API/Program.cs index bcf000d..b29ac45 100644 --- a/LeedsExperiment/Preservation.API/Program.cs +++ b/LeedsExperiment/Preservation.API/Program.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Microsoft.OpenApi.Models; using Preservation; +using Preservation.API.Models; using PreservationApiClient; var builder = WebApplication.CreateBuilder(args); @@ -12,17 +13,21 @@ .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault; }); -builder.Services.AddHttpLogging(o => { }); - builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri(builder.Configuration["StorageApiBaseAddress"]!); client.DefaultRequestHeaders.Add("Accept", "application/json"); }); +builder.Services + .AddHttpContextAccessor() + .AddHttpLogging(o => { }) + .AddScoped() + .AddScoped(); + builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(opts => { diff --git a/LeedsExperiment/Preservation/FedoraWrapper.cs b/LeedsExperiment/Preservation/FedoraWrapper.cs index 0349507..8d0f76e 100644 --- a/LeedsExperiment/Preservation/FedoraWrapper.cs +++ b/LeedsExperiment/Preservation/FedoraWrapper.cs @@ -159,7 +159,7 @@ private Container MakeContainer(bool isArchivalGroup, FedoraJsonLdResponse conta return new ArchivalGroup(containerResponse) { ObjectPath = GetObjectPath(containerResponse.Id), - PreservationApiUri = GetApiUri(containerResponse.Id) + StorageApiUri = GetApiUri(containerResponse.Id) }; } else @@ -167,7 +167,7 @@ private Container MakeContainer(bool isArchivalGroup, FedoraJsonLdResponse conta return new Container(containerResponse) { ObjectPath = GetObjectPath(containerResponse.Id), - PreservationApiUri = GetApiUri(containerResponse.Id) + StorageApiUri = GetApiUri(containerResponse.Id) }; } } @@ -177,7 +177,7 @@ private Binary MakeBinary(BinaryMetadataResponse binaryResponse) return new Binary(binaryResponse) { ObjectPath = GetObjectPath(binaryResponse.Id), - PreservationApiUri = GetApiUri(binaryResponse.Id) + StorageApiUri = GetApiUri(binaryResponse.Id) }; } @@ -902,7 +902,7 @@ private List GetIdsFromContainsProperty(JsonElement element) container = new Container(fedoraContainer) { ObjectPath = GetObjectPath(fedoraContainer.Id), - PreservationApiUri = GetApiUri(fedoraContainer.Id) + StorageApiUri = GetApiUri(fedoraContainer.Id) }; } topContainer.Containers.Add(container!); @@ -913,7 +913,7 @@ private List GetIdsFromContainsProperty(JsonElement element) var binary = new Binary(fedoraBinary!) { ObjectPath = GetObjectPath(fedoraBinary!.Id), - PreservationApiUri = GetApiUri(fedoraBinary.Id) + StorageApiUri = GetApiUri(fedoraBinary.Id) }; topContainer.Binaries.Add(binary); }