diff --git a/src/AVPRIndex/AVPRIndex.fsproj b/src/AVPRIndex/AVPRIndex.fsproj index 1c7b46b..b26a55e 100644 --- a/src/AVPRIndex/AVPRIndex.fsproj +++ b/src/AVPRIndex/AVPRIndex.fsproj @@ -5,7 +5,23 @@ true + + Kevin Schneider + Type system for the indexing backend of avpr.nfdi4plants.org + Type system for the indexing backend of avpr.nfdi4plants.org + MIT + C# F# ARC annotated-research-context rdm research-data-management validation + https://github.com/nfdi4plants/arc-validate-package-registry + https://github.com/nfdi4plants/arc-validate-package-registry + git + $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/RELEASE_NOTES.md")) + README.md + 0.0.1 + + + + diff --git a/src/AVPRIndex/Domain.fs b/src/AVPRIndex/Domain.fs index 925a837..5dda8a0 100644 --- a/src/AVPRIndex/Domain.fs +++ b/src/AVPRIndex/Domain.fs @@ -39,9 +39,37 @@ module Domain = ) | _ -> false + type OntologyAnnotation() = + + member val Name = "" with get,set + member val TermSourceREF = "" with get,set + member val TermAccessionNumber = "" with get,set + + override this.GetHashCode() = + hash ( + this.Name, + this.TermSourceREF, + this.TermAccessionNumber + ) + + override this.Equals(other) = + match other with + | :? OntologyAnnotation as oa -> + ( + this.Name, + this.TermSourceREF, + this.TermAccessionNumber + ) = ( + oa.Name, + oa.TermSourceREF, + oa.TermAccessionNumber + ) + | _ -> false + type ValidationPackageMetadata() = // mandatory fields member val Name = "" with get,set + member val Summary = "" with get,set member val Description = "" with get,set member val MajorVersion = 0 with get,set member val MinorVersion = 0 with get,set @@ -49,12 +77,13 @@ module Domain = // optional fields member val Publish = false with get,set member val Authors: Author [] = Array.empty with get,set - member val Tags: string [] = Array.empty with get,set + member val Tags: OntologyAnnotation [] = Array.empty with get,set member val ReleaseNotes = "" with get,set override this.GetHashCode() = hash ( this.Name, + this.Summary, this.Description, this.MajorVersion, this.MinorVersion, @@ -70,6 +99,7 @@ module Domain = | :? ValidationPackageMetadata as vpm -> ( this.Name, + this.Summary, this.Description, this.MajorVersion, this.MinorVersion, @@ -80,6 +110,7 @@ module Domain = this.ReleaseNotes ) = ( vpm.Name, + vpm.Summary, vpm.Description, vpm.MajorVersion, vpm.MinorVersion, diff --git a/src/AVPRIndex/README.md b/src/AVPRIndex/README.md new file mode 100644 index 0000000..7d91980 --- /dev/null +++ b/src/AVPRIndex/README.md @@ -0,0 +1,5 @@ +# AVPR Index + +Type system for the indexing backend of https://avpr.nfdi4plants.org. + +This lib exposes the type system and utilities needed to parse and index arc validation packages with yaml frontmatter. \ No newline at end of file diff --git a/src/AVPRIndex/RELEASE_NOTES.md b/src/AVPRIndex/RELEASE_NOTES.md new file mode 100644 index 0000000..e48f2bb --- /dev/null +++ b/src/AVPRIndex/RELEASE_NOTES.md @@ -0,0 +1,3 @@ +## v0.0.1 + +- Initial release for AVPR API v1 \ No newline at end of file diff --git a/src/PackageRegistryService/Data/DataInitializer.cs b/src/PackageRegistryService/Data/DataInitializer.cs index 5659bd7..ac66637 100644 --- a/src/PackageRegistryService/Data/DataInitializer.cs +++ b/src/PackageRegistryService/Data/DataInitializer.cs @@ -10,7 +10,7 @@ using static AVPRIndex.Frontmatter; namespace PackageRegistryService.Data - + { public class DataInitializer { @@ -18,7 +18,7 @@ public static List ReadIndex() { var json = File.ReadAllText(@"Data/arc-validate-package-index.json"); var index = JsonSerializer.Deserialize>(json); - return index; + return index ?? []; } public static void SeedData(ValidationPackageDb context) { @@ -39,6 +39,7 @@ public static void SeedData(ValidationPackageDb context) return new ValidationPackage { Name = i.Metadata.Name, + Summary = i.Metadata.Summary, Description = i.Metadata.Description, MajorVersion = i.Metadata.MajorVersion, MinorVersion = i.Metadata.MinorVersion, diff --git a/src/PackageRegistryService/Data/arc-validate-package-index.json b/src/PackageRegistryService/Data/arc-validate-package-index.json index 910ec9b..f8746bb 100644 --- a/src/PackageRegistryService/Data/arc-validate-package-index.json +++ b/src/PackageRegistryService/Data/arc-validate-package-index.json @@ -2,7 +2,7 @@ { "RepoPath": "src/PackageRegistryService/StagingArea/invenio/invenio@1.0.0.fsx", "FileName": "invenio@1.0.0.fsx", - "LastUpdated": "2024-02-29T14:45:26+01:00", + "LastUpdated": "2024-02-29T14:45:24+01:00", "ContentHash": "864458DA6C7B0F08A546210CFD3CCA4A", "Metadata": { "Name": "invenio", @@ -74,7 +74,7 @@ { "RepoPath": "src/PackageRegistryService/StagingArea/test/test@2.0.0.fsx", "FileName": "test@2.0.0.fsx", - "LastUpdated": "2024-02-29T14:45:26+01:00", + "LastUpdated": "2024-02-29T14:45:24+01:00", "ContentHash": "40729E451689807AEFF17F5932843A4C", "Metadata": { "Name": "test", @@ -121,7 +121,7 @@ { "RepoPath": "src/PackageRegistryService/StagingArea/test/test@3.0.0.fsx", "FileName": "test@3.0.0.fsx", - "LastUpdated": "2024-02-29T14:45:26+01:00", + "LastUpdated": "2024-02-29T14:45:24+01:00", "ContentHash": "0537642158095CE84F4FA8363225831E", "Metadata": { "Name": "test", diff --git a/src/PackageRegistryService/Migrations/20240229124121_AddSummaryAndOntologyTags.Designer.cs b/src/PackageRegistryService/Migrations/20240229124121_AddSummaryAndOntologyTags.Designer.cs new file mode 100644 index 0000000..0ec1b68 --- /dev/null +++ b/src/PackageRegistryService/Migrations/20240229124121_AddSummaryAndOntologyTags.Designer.cs @@ -0,0 +1,174 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PackageRegistryService.Models; + +#nullable disable + +namespace PackageRegistryService.Migrations +{ + [DbContext(typeof(ValidationPackageDb))] + [Migration("20240229124121_AddSummaryAndOntologyTags")] + partial class AddSummaryAndOntologyTags + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PackageRegistryService.Models.PackageContentHash", b => + { + b.Property("PackageName") + .HasColumnType("text"); + + b.Property("PackageMajorVersion") + .HasColumnType("integer"); + + b.Property("PackageMinorVersion") + .HasColumnType("integer"); + + b.Property("PackagePatchVersion") + .HasColumnType("integer"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("PackageName", "PackageMajorVersion", "PackageMinorVersion", "PackagePatchVersion"); + + b.ToTable("Hashes"); + }); + + modelBuilder.Entity("PackageRegistryService.Models.ValidationPackage", b => + { + b.Property("Name") + .HasColumnType("text"); + + b.Property("MajorVersion") + .HasColumnType("integer"); + + b.Property("MinorVersion") + .HasColumnType("integer"); + + b.Property("PatchVersion") + .HasColumnType("integer"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("PackageContent") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("ReleaseDate") + .HasColumnType("date"); + + b.Property("ReleaseNotes") + .HasColumnType("text"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Name", "MajorVersion", "MinorVersion", "PatchVersion"); + + b.ToTable("ValidationPackages"); + }); + + modelBuilder.Entity("PackageRegistryService.Models.ValidationPackage", b => + { + b.OwnsMany("AVPRIndex.Domain+Author", "Authors", b1 => + { + b1.Property("ValidationPackageName") + .HasColumnType("text"); + + b1.Property("ValidationPackageMajorVersion") + .HasColumnType("integer"); + + b1.Property("ValidationPackageMinorVersion") + .HasColumnType("integer"); + + b1.Property("ValidationPackagePatchVersion") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Affiliation") + .HasColumnType("text"); + + b1.Property("AffiliationLink") + .HasColumnType("text"); + + b1.Property("Email") + .HasColumnType("text"); + + b1.Property("FullName") + .HasColumnType("text"); + + b1.HasKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion", "Id"); + + b1.ToTable("ValidationPackages"); + + b1.ToJson("Authors"); + + b1.WithOwner() + .HasForeignKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion"); + }); + + b.OwnsMany("AVPRIndex.Domain+OntologyAnnotation", "Tags", b1 => + { + b1.Property("ValidationPackageName") + .HasColumnType("text"); + + b1.Property("ValidationPackageMajorVersion") + .HasColumnType("integer"); + + b1.Property("ValidationPackageMinorVersion") + .HasColumnType("integer"); + + b1.Property("ValidationPackagePatchVersion") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Name") + .HasColumnType("text"); + + b1.Property("TermAccessionNumber") + .HasColumnType("text"); + + b1.Property("TermSourceREF") + .HasColumnType("text"); + + b1.HasKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion", "Id"); + + b1.ToTable("ValidationPackages"); + + b1.ToJson("Tags"); + + b1.WithOwner() + .HasForeignKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion"); + }); + + b.Navigation("Authors"); + + b.Navigation("Tags"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/PackageRegistryService/Migrations/20240229124121_AddSummaryAndOntologyTags.cs b/src/PackageRegistryService/Migrations/20240229124121_AddSummaryAndOntologyTags.cs new file mode 100644 index 0000000..d6c5216 --- /dev/null +++ b/src/PackageRegistryService/Migrations/20240229124121_AddSummaryAndOntologyTags.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PackageRegistryService.Migrations +{ + /// + public partial class AddSummaryAndOntologyTags : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + migrationBuilder.Sql(@" + CREATE FUNCTION transform_tags(tags text[]) RETURNS jsonb AS $$ + BEGIN + RETURN jsonb_agg(jsonb_build_object('Name', tag)) FROM unnest(tags) AS tag; + END; + $$ LANGUAGE plpgsql; + "); + + migrationBuilder.Sql(@" + ALTER TABLE ""ValidationPackages"" + ADD COLUMN ""TMP"" jsonb + "); + + migrationBuilder.Sql(@" + UPDATE ""ValidationPackages"" + SET ""TMP"" = transform_tags(""Tags"") + "); + + migrationBuilder.Sql(@" + ALTER TABLE ""ValidationPackages"" + DROP COLUMN ""Tags""; + ALTER TABLE ""ValidationPackages"" + RENAME COLUMN ""TMP"" TO ""Tags""; + "); + + migrationBuilder.AddColumn( + name: "Summary", + table: "ValidationPackages", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + DROP FUNCTION IF EXISTS transform_tags(text[]); + "); + + migrationBuilder.Sql(@" + ALTER TABLE ""ValidationPackages"" + ADD COLUMN ""TMP"" text[] + "); + + migrationBuilder.Sql(@" + UPDATE ""ValidationPackages"" + SET ""TMP"" = ( + SELECT array_agg(tag->>'Name') FROM jsonb_array_elements(""Tags"") AS tag + ) + "); + + migrationBuilder.Sql(@" + ALTER TABLE ""ValidationPackages"" + DROP COLUMN ""Tags""; + ALTER TABLE ""ValidationPackages"" + RENAME COLUMN ""TMP"" TO ""Tags""; + "); + + migrationBuilder.AlterColumn( + name: "Tags", + table: "ValidationPackages", + type: "text[]", + nullable: true, + oldClrType: typeof(string), + oldType: "jsonb", + oldNullable: true); + } + } +} diff --git a/src/PackageRegistryService/Migrations/ValidationPackageDbModelSnapshot.cs b/src/PackageRegistryService/Migrations/ValidationPackageDbModelSnapshot.cs index 1deb89c..6f92400 100644 --- a/src/PackageRegistryService/Migrations/ValidationPackageDbModelSnapshot.cs +++ b/src/PackageRegistryService/Migrations/ValidationPackageDbModelSnapshot.cs @@ -73,8 +73,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ReleaseNotes") .HasColumnType("text"); - b.Property("Tags") - .HasColumnType("text[]"); + b.Property("Summary") + .IsRequired() + .HasColumnType("text"); b.HasKey("Name", "MajorVersion", "MinorVersion", "PatchVersion"); @@ -83,7 +84,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("PackageRegistryService.Models.ValidationPackage", b => { - b.OwnsMany("PackageRegistryService.Models.Author", "Authors", b1 => + b.OwnsMany("AVPRIndex.Domain+Author", "Authors", b1 => { b1.Property("ValidationPackageName") .HasColumnType("text"); @@ -102,19 +103,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("integer"); b1.Property("Affiliation") - .IsRequired() .HasColumnType("text"); b1.Property("AffiliationLink") - .IsRequired() .HasColumnType("text"); b1.Property("Email") - .IsRequired() .HasColumnType("text"); b1.Property("FullName") - .IsRequired() .HasColumnType("text"); b1.HasKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion", "Id"); @@ -127,7 +124,46 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion"); }); + b.OwnsMany("AVPRIndex.Domain+OntologyAnnotation", "Tags", b1 => + { + b1.Property("ValidationPackageName") + .HasColumnType("text"); + + b1.Property("ValidationPackageMajorVersion") + .HasColumnType("integer"); + + b1.Property("ValidationPackageMinorVersion") + .HasColumnType("integer"); + + b1.Property("ValidationPackagePatchVersion") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Name") + .HasColumnType("text"); + + b1.Property("TermAccessionNumber") + .HasColumnType("text"); + + b1.Property("TermSourceREF") + .HasColumnType("text"); + + b1.HasKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion", "Id"); + + b1.ToTable("ValidationPackages"); + + b1.ToJson("Tags"); + + b1.WithOwner() + .HasForeignKey("ValidationPackageName", "ValidationPackageMajorVersion", "ValidationPackageMinorVersion", "ValidationPackagePatchVersion"); + }); + b.Navigation("Authors"); + + b.Navigation("Tags"); }); #pragma warning restore 612, 618 } diff --git a/src/PackageRegistryService/Models/ValidationPackage.cs b/src/PackageRegistryService/Models/ValidationPackage.cs index ae5d360..9e815e3 100644 --- a/src/PackageRegistryService/Models/ValidationPackage.cs +++ b/src/PackageRegistryService/Models/ValidationPackage.cs @@ -20,9 +20,18 @@ public class ValidationPackage [Key] public required string Name { get; set; } /// - /// Free text validation package description. + /// Single sentence validation package description. /// /// MyPackage does the thing + public required string Summary { get; set; } + /// + /// Free text validation package description. + /// + /// + /// MyPackage does the thing. + /// It does it very good, it does it very well. + /// It does it very fast, it does it very swell. + /// public required string Description { get; set; } /// /// SemVer major version of the validation package. @@ -54,7 +63,7 @@ public class ValidationPackage /// /// /// - public string[]? Tags { get; set; } + public ICollection? Tags { get; set; } /// /// /// diff --git a/src/PackageRegistryService/Models/ValidationPackageDb.cs b/src/PackageRegistryService/Models/ValidationPackageDb.cs index 5898eae..eb8dfec 100644 --- a/src/PackageRegistryService/Models/ValidationPackageDb.cs +++ b/src/PackageRegistryService/Models/ValidationPackageDb.cs @@ -19,6 +19,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OwnsMany(v => v.Authors, a => { a.ToJson(); + }) + .OwnsMany(v => v.Tags, t => + { + t.ToJson(); }); } } diff --git a/src/PackageRegistryService/PackageRegistryService.csproj b/src/PackageRegistryService/PackageRegistryService.csproj index b8aad75..fec1b0c 100644 --- a/src/PackageRegistryService/PackageRegistryService.csproj +++ b/src/PackageRegistryService/PackageRegistryService.csproj @@ -29,6 +29,7 @@ + diff --git a/src/PackageRegistryService/Pages/Handlers/PackageHandlers.cs b/src/PackageRegistryService/Pages/Handlers/PackageHandlers.cs index baae68b..a92a5d4 100644 --- a/src/PackageRegistryService/Pages/Handlers/PackageHandlers.cs +++ b/src/PackageRegistryService/Pages/Handlers/PackageHandlers.cs @@ -48,7 +48,7 @@ public static async Task packageVersion: package.GetSemanticVersionString(), packageContent: Encoding.UTF8.GetString(package.PackageContent), packageReleaseDate: package.ReleaseDate, - packageTags: package.Tags ?? [], + packageTags: (package.Tags ?? []).Select(t => t.Name).ToArray(), packageDescription: package.Description, packageReleaseNotes: package.ReleaseNotes ?? "", packageAuthors: (package.Authors ?? []).ToArray(), @@ -85,7 +85,7 @@ public static async Task> RenderLatest(stri packageVersion: latestPackage.GetSemanticVersionString(), packageContent: Encoding.UTF8.GetString(latestPackage.PackageContent), packageReleaseDate: latestPackage.ReleaseDate, - packageTags: latestPackage.Tags ?? [], + packageTags: (latestPackage.Tags ?? []).Select(t => t.Name).ToArray(), packageDescription: latestPackage.Description, packageReleaseNotes: latestPackage.ReleaseNotes ?? "", packageAuthors: (latestPackage.Authors ?? []).ToArray(), diff --git a/src/PackageRegistryService/Pages/Handlers/PackagesHandlers.cs b/src/PackageRegistryService/Pages/Handlers/PackagesHandlers.cs index b1ca2a5..9c48544 100644 --- a/src/PackageRegistryService/Pages/Handlers/PackagesHandlers.cs +++ b/src/PackageRegistryService/Pages/Handlers/PackagesHandlers.cs @@ -37,7 +37,7 @@ public static async Task Render(ValidationPackageDb database) return new PackageSummary( Name: group.Key, - Tags: latestPackage.Tags ?? [], + Tags: (latestPackage.Tags ?? []).Select(t => t.Name).ToArray(), Description: latestPackage.Description, ReleaseDate: latestPackage.ReleaseDate, LatestVersion: latestPackage.GetSemanticVersionString()