From 833d0487f2dcffba16db3279dde5d1c9ccec99e8 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Fri, 7 Jul 2023 19:30:53 -0500 Subject: [PATCH 1/6] Implement MySql storage for package contents Add a new storage type option that stores package contents in MySql along with the package metadata. Though MySql is not the best for storing large file contents, this is an alternative to storing package contents in the file system, which should be better for highly-available/distributed use cases. --- .../Entities/IPackageContentsContext.cs | 13 ++++ src/BaGet.Core/Entities/PackageContents.cs | 11 +++ .../Extensions/BaGetApplicationExtensions.cs | 6 ++ .../DependencyInjectionExtensions.cs | 6 ++ .../Storage/DatabaseStorageService.cs | 69 +++++++++++++++++++ .../MySqlApplicationExtensions.cs | 2 + src/BaGet.Database.MySql/MySqlContext.cs | 21 +++++- 7 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/BaGet.Core/Entities/IPackageContentsContext.cs create mode 100644 src/BaGet.Core/Entities/PackageContents.cs create mode 100644 src/BaGet.Core/Storage/DatabaseStorageService.cs diff --git a/src/BaGet.Core/Entities/IPackageContentsContext.cs b/src/BaGet.Core/Entities/IPackageContentsContext.cs new file mode 100644 index 00000000..add10b6a --- /dev/null +++ b/src/BaGet.Core/Entities/IPackageContentsContext.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Core +{ + public interface IPackageContentsContext + { + DbSet PackageContents { get; set; } + + Task SaveChangesAsync(CancellationToken cancellationToken); + } +} diff --git a/src/BaGet.Core/Entities/PackageContents.cs b/src/BaGet.Core/Entities/PackageContents.cs new file mode 100644 index 00000000..6c5a9ec2 --- /dev/null +++ b/src/BaGet.Core/Entities/PackageContents.cs @@ -0,0 +1,11 @@ +namespace BaGet.Core +{ + public class PackageContents + { + public int Key { get; set; } + + public string Path { get; set; } + + public byte[] Data { get; set; } + } +} diff --git a/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs b/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs index 7dc2967f..822c5611 100644 --- a/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs +++ b/src/BaGet.Core/Extensions/BaGetApplicationExtensions.cs @@ -22,6 +22,12 @@ public static BaGetApplication AddFileStorage( return app; } + public static BaGetApplication AddMySqlStorage(this BaGetApplication app) + { + app.Services.TryAddTransient(provider => provider.GetRequiredService()); + return app; + } + public static BaGetApplication AddNullStorage(this BaGetApplication app) { app.Services.TryAddTransient(provider => provider.GetRequiredService()); diff --git a/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs b/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs index a56c41ea..0aa8de2a 100644 --- a/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs +++ b/src/BaGet.Core/Extensions/DependencyInjectionExtensions.cs @@ -100,6 +100,7 @@ private static void AddBaGetServices(this IServiceCollection services) services.TryAddTransient(); services.TryAddTransient(); + services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); @@ -133,6 +134,11 @@ private static void AddDefaultProviders(this IServiceCollection services) return provider.GetRequiredService(); } + if (configuration.HasStorageType("mysql")) + { + return provider.GetRequiredService(); + } + if (configuration.HasStorageType("null")) { return provider.GetRequiredService(); diff --git a/src/BaGet.Core/Storage/DatabaseStorageService.cs b/src/BaGet.Core/Storage/DatabaseStorageService.cs new file mode 100644 index 00000000..7f876d98 --- /dev/null +++ b/src/BaGet.Core/Storage/DatabaseStorageService.cs @@ -0,0 +1,69 @@ +using System.Linq; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace BaGet.Core +{ + public class DatabaseStorageService : IStorageService + { + private readonly IPackageContentsContext _context; + + public DatabaseStorageService(IPackageContentsContext context) + { + if (context == null) throw new ArgumentException(nameof(context)); + + _context = context; + } + + public async Task DeleteAsync(string path, CancellationToken cancellationToken = default) + { + var contents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + if (contents != null) + { + _context.PackageContents.Remove(contents); + await _context.SaveChangesAsync(cancellationToken); + } + } + + public async Task GetAsync(string path, CancellationToken cancellationToken = default) + { + var contents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + var ms = new MemoryStream(contents.Data); + return ms; + } + + public Task GetDownloadUriAsync(string path, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public async Task PutAsync(string path, Stream content, string contentType, CancellationToken cancellationToken = default) + { + byte[] newData; + using (var binaryReader = new BinaryReader(content)) + { + newData = binaryReader.ReadBytes((int)content.Length); + } + + var existingContents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + if (existingContents != null) + { + return existingContents.Data.SequenceEqual(newData) + ? StoragePutResult.AlreadyExists + : StoragePutResult.Success; + } + + _context.PackageContents.Add(new PackageContents + { + Path = path, + Data = newData, + }); + await _context.SaveChangesAsync(cancellationToken); + + return StoragePutResult.Success; + } + } +} diff --git a/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs b/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs index 2156d90c..c0fb5f2e 100644 --- a/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs +++ b/src/BaGet.Database.MySql/MySqlApplicationExtensions.cs @@ -18,6 +18,8 @@ public static BaGetApplication AddMySqlDatabase(this BaGetApplication app) options.UseMySql(databaseOptions.Value.ConnectionString); }); + app.Services.AddTransient(services => services.GetRequiredService()); + return app; } diff --git a/src/BaGet.Database.MySql/MySqlContext.cs b/src/BaGet.Database.MySql/MySqlContext.cs index 49bbb65f..e297cde7 100644 --- a/src/BaGet.Database.MySql/MySqlContext.cs +++ b/src/BaGet.Database.MySql/MySqlContext.cs @@ -1,16 +1,19 @@ using BaGet.Core; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using MySql.Data.MySqlClient; namespace BaGet.Database.MySql { - public class MySqlContext : AbstractContext + public class MySqlContext : AbstractContext, IPackageContentsContext { /// /// The MySQL Server error code for when a unique constraint is violated. /// private const int UniqueConstraintViolationErrorCode = 1062; + public DbSet PackageContents { get; set; } + public MySqlContext(DbContextOptions options) : base(options) { } @@ -26,5 +29,21 @@ public override bool IsUniqueConstraintViolationException(DbUpdateException exce /// See: https://dev.mysql.com/doc/refman/8.0/en/subquery-restrictions.html /// public override bool SupportsLimitInSubqueries => false; + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity(BuildPackageContentsEntity); + } + + private void BuildPackageContentsEntity(EntityTypeBuilder packageContents) + { + packageContents.HasKey(p => p.Key); + packageContents.HasIndex(p => new { p.Path }).IsUnique(); + + packageContents.Property(p => p.Path) + .HasMaxLength((MaxPackageIdLength + MaxPackageVersionLength) * 2 + 20); + } } } From f56a58e7753730ef093729d55e0dd7d52395a3df Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Mon, 10 Jul 2023 14:58:55 +0000 Subject: [PATCH 2/6] Add migration --- ...506_CreatePackageContentsTable.Designer.cs | 261 ++++++++++++++++++ ...230710145506_CreatePackageContentsTable.cs | 58 ++++ .../Migrations/MySqlContextModelSnapshot.cs | 21 ++ 3 files changed, 340 insertions(+) create mode 100644 src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs create mode 100644 src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs new file mode 100644 index 00000000..85a39ed5 --- /dev/null +++ b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs @@ -0,0 +1,261 @@ +// +using System; +using BaGet.Database.MySql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BaGet.Database.MySql.Migrations +{ + [DbContext(typeof(MySqlContext))] + [Migration("20230710145506_CreatePackageContentsTable")] + partial class CreatePackageContentsTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.18") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BaGet.Core.Package", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Authors") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Description") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Downloads") + .HasColumnType("bigint"); + + b.Property("HasEmbeddedIcon") + .HasColumnType("tinyint(1)"); + + b.Property("HasReadme") + .HasColumnType("tinyint(1)"); + + b.Property("IconUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Id") + .IsRequired() + .HasColumnType("varchar(128) CHARACTER SET utf8mb4") + .HasMaxLength(128); + + b.Property("IsPrerelease") + .HasColumnType("tinyint(1)"); + + b.Property("Language") + .HasColumnType("varchar(20) CHARACTER SET utf8mb4") + .HasMaxLength(20); + + b.Property("LicenseUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Listed") + .HasColumnType("tinyint(1)"); + + b.Property("MinClientVersion") + .HasColumnType("varchar(44) CHARACTER SET utf8mb4") + .HasMaxLength(44); + + b.Property("NormalizedVersionString") + .IsRequired() + .HasColumnName("Version") + .HasColumnType("varchar(64) CHARACTER SET utf8mb4") + .HasMaxLength(64); + + b.Property("OriginalVersionString") + .HasColumnName("OriginalVersion") + .HasColumnType("varchar(64) CHARACTER SET utf8mb4") + .HasMaxLength(64); + + b.Property("ProjectUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Published") + .HasColumnType("datetime(6)"); + + b.Property("ReleaseNotes") + .HasColumnName("ReleaseNotes") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RepositoryType") + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("RepositoryUrl") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("RequireLicenseAcceptance") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("timestamp(6)"); + + b.Property("SemVerLevel") + .HasColumnType("int"); + + b.Property("Summary") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Tags") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(4000); + + b.Property("Title") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("Id", "NormalizedVersionString") + .IsUnique(); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("BaGet.Core.PackageContents", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Data") + .HasColumnType("longblob"); + + b.Property("Path") + .HasColumnType("varchar(404) CHARACTER SET utf8mb4") + .HasMaxLength(404); + + b.HasKey("Key"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("PackageContents"); + }); + + modelBuilder.Entity("BaGet.Core.PackageDependency", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Id") + .HasColumnType("varchar(128) CHARACTER SET utf8mb4") + .HasMaxLength(128); + + b.Property("PackageKey") + .HasColumnType("int"); + + b.Property("TargetFramework") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.Property("VersionRange") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.HasKey("Key"); + + b.HasIndex("Id"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageDependencies"); + }); + + modelBuilder.Entity("BaGet.Core.PackageType", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("varchar(512) CHARACTER SET utf8mb4") + .HasMaxLength(512); + + b.Property("PackageKey") + .HasColumnType("int"); + + b.Property("Version") + .HasColumnType("varchar(64) CHARACTER SET utf8mb4") + .HasMaxLength(64); + + b.HasKey("Key"); + + b.HasIndex("Name"); + + b.HasIndex("PackageKey"); + + b.ToTable("PackageTypes"); + }); + + modelBuilder.Entity("BaGet.Core.TargetFramework", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Moniker") + .HasColumnType("varchar(256) CHARACTER SET utf8mb4") + .HasMaxLength(256); + + b.Property("PackageKey") + .HasColumnType("int"); + + b.HasKey("Key"); + + b.HasIndex("Moniker"); + + b.HasIndex("PackageKey"); + + b.ToTable("TargetFrameworks"); + }); + + modelBuilder.Entity("BaGet.Core.PackageDependency", b => + { + b.HasOne("BaGet.Core.Package", "Package") + .WithMany("Dependencies") + .HasForeignKey("PackageKey"); + }); + + modelBuilder.Entity("BaGet.Core.PackageType", b => + { + b.HasOne("BaGet.Core.Package", "Package") + .WithMany("PackageTypes") + .HasForeignKey("PackageKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BaGet.Core.TargetFramework", b => + { + b.HasOne("BaGet.Core.Package", "Package") + .WithMany("TargetFrameworks") + .HasForeignKey("PackageKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs new file mode 100644 index 00000000..1e8cc116 --- /dev/null +++ b/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BaGet.Database.MySql.Migrations +{ + public partial class CreatePackageContentsTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "RowVersion", + table: "Packages", + rowVersion: true, + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp(6)", + oldNullable: true) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn); + + migrationBuilder.CreateTable( + name: "PackageContents", + columns: table => new + { + Key = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Path = table.Column(maxLength: 404, nullable: true), + Data = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageContents", x => x.Key); + }); + + migrationBuilder.CreateIndex( + name: "IX_PackageContents_Path", + table: "PackageContents", + column: "Path", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PackageContents"); + + migrationBuilder.AlterColumn( + name: "RowVersion", + table: "Packages", + type: "timestamp(6)", + nullable: true, + oldClrType: typeof(DateTime), + oldRowVersion: true, + oldNullable: true) + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn); + } + } +} diff --git a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs index d9a6e93c..8c35aaa5 100644 --- a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs +++ b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs @@ -130,6 +130,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Packages"); }); + modelBuilder.Entity("BaGet.Core.PackageContents", b => + { + b.Property("Key") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Data") + .HasColumnType("longblob"); + + b.Property("Path") + .HasColumnType("varchar(404) CHARACTER SET utf8mb4") + .HasMaxLength(404); + + b.HasKey("Key"); + + b.HasIndex("Path") + .IsUnique(); + + b.ToTable("PackageContents"); + }); + modelBuilder.Entity("BaGet.Core.PackageDependency", b => { b.Property("Key") From e79e65c630da991799e345ec7f9bec0d35caf387 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Mon, 10 Jul 2023 15:23:14 +0000 Subject: [PATCH 3/6] fix migration; add valid storage type --- ...=> 20230710152239_CreatePackageContentsTable.Designer.cs} | 5 ++--- ...Table.cs => 20230710152239_CreatePackageContentsTable.cs} | 2 +- .../Migrations/MySqlContextModelSnapshot.cs | 3 +-- src/BaGet.Database.MySql/MySqlContext.cs | 2 +- src/BaGet/ConfigureBaGetOptions.cs | 1 + 5 files changed, 6 insertions(+), 7 deletions(-) rename src/BaGet.Database.MySql/Migrations/{20230710145506_CreatePackageContentsTable.Designer.cs => 20230710152239_CreatePackageContentsTable.Designer.cs} (98%) rename src/BaGet.Database.MySql/Migrations/{20230710145506_CreatePackageContentsTable.cs => 20230710152239_CreatePackageContentsTable.cs} (95%) diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.Designer.cs similarity index 98% rename from src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs rename to src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.Designer.cs index 85a39ed5..b039b6fa 100644 --- a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.Designer.cs +++ b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.Designer.cs @@ -9,7 +9,7 @@ namespace BaGet.Database.MySql.Migrations { [DbContext(typeof(MySqlContext))] - [Migration("20230710145506_CreatePackageContentsTable")] + [Migration("20230710152239_CreatePackageContentsTable")] partial class CreatePackageContentsTable { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -142,8 +142,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("longblob"); b.Property("Path") - .HasColumnType("varchar(404) CHARACTER SET utf8mb4") - .HasMaxLength(404); + .HasColumnType("varchar(255)"); b.HasKey("Key"); diff --git a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.cs similarity index 95% rename from src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs rename to src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.cs index 1e8cc116..3d4fdd72 100644 --- a/src/BaGet.Database.MySql/Migrations/20230710145506_CreatePackageContentsTable.cs +++ b/src/BaGet.Database.MySql/Migrations/20230710152239_CreatePackageContentsTable.cs @@ -24,7 +24,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Key = table.Column(nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Path = table.Column(maxLength: 404, nullable: true), + Path = table.Column(type: "varchar(255)", nullable: true), Data = table.Column(nullable: true) }, constraints: table => diff --git a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs index 8c35aaa5..85314793 100644 --- a/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs +++ b/src/BaGet.Database.MySql/Migrations/MySqlContextModelSnapshot.cs @@ -140,8 +140,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("longblob"); b.Property("Path") - .HasColumnType("varchar(404) CHARACTER SET utf8mb4") - .HasMaxLength(404); + .HasColumnType("varchar(255)"); b.HasKey("Key"); diff --git a/src/BaGet.Database.MySql/MySqlContext.cs b/src/BaGet.Database.MySql/MySqlContext.cs index e297cde7..55426281 100644 --- a/src/BaGet.Database.MySql/MySqlContext.cs +++ b/src/BaGet.Database.MySql/MySqlContext.cs @@ -43,7 +43,7 @@ private void BuildPackageContentsEntity(EntityTypeBuilder packa packageContents.HasIndex(p => new { p.Path }).IsUnique(); packageContents.Property(p => p.Path) - .HasMaxLength((MaxPackageIdLength + MaxPackageVersionLength) * 2 + 20); + .HasColumnType("varchar(255)"); } } } diff --git a/src/BaGet/ConfigureBaGetOptions.cs b/src/BaGet/ConfigureBaGetOptions.cs index b28a658d..254afaf2 100644 --- a/src/BaGet/ConfigureBaGetOptions.cs +++ b/src/BaGet/ConfigureBaGetOptions.cs @@ -41,6 +41,7 @@ private static readonly HashSet ValidStorageTypes "AzureBlobStorage", "Filesystem", "GoogleCloud", + "MySql", "Null", }; From d77ef4df5565217385dc9f858b417d4b2e8defbc Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Mon, 10 Jul 2023 16:02:01 +0000 Subject: [PATCH 4/6] error handling --- src/BaGet.Core/Storage/DatabaseStorageService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/BaGet.Core/Storage/DatabaseStorageService.cs b/src/BaGet.Core/Storage/DatabaseStorageService.cs index 7f876d98..31395ea8 100644 --- a/src/BaGet.Core/Storage/DatabaseStorageService.cs +++ b/src/BaGet.Core/Storage/DatabaseStorageService.cs @@ -31,6 +31,10 @@ public async Task DeleteAsync(string path, CancellationToken cancellationToken = public async Task GetAsync(string path, CancellationToken cancellationToken = default) { var contents = await _context.PackageContents.SingleOrDefaultAsync(p => p.Path == path, cancellationToken); + if (contents == null) + { + throw new Exception($"PackageContents record not found for path: {path}"); + } var ms = new MemoryStream(contents.Data); return ms; } From b96b0f85e44d921dc844d41991afcc5466c34a35 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Tue, 11 Jul 2023 13:35:47 -0500 Subject: [PATCH 5/6] Use conflict --- src/BaGet.Core/Storage/DatabaseStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BaGet.Core/Storage/DatabaseStorageService.cs b/src/BaGet.Core/Storage/DatabaseStorageService.cs index 31395ea8..7206cfc8 100644 --- a/src/BaGet.Core/Storage/DatabaseStorageService.cs +++ b/src/BaGet.Core/Storage/DatabaseStorageService.cs @@ -57,7 +57,7 @@ public async Task PutAsync(string path, Stream content, string { return existingContents.Data.SequenceEqual(newData) ? StoragePutResult.AlreadyExists - : StoragePutResult.Success; + : StoragePutResult.Conflict; } _context.PackageContents.Add(new PackageContents From 23fbe04c62b31477cbe901e7cdca46fb8a720bc7 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Tue, 11 Jul 2023 15:00:52 -0500 Subject: [PATCH 6/6] addmysqlstorage --- src/BaGet/Startup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BaGet/Startup.cs b/src/BaGet/Startup.cs index f637267b..ff70fb35 100644 --- a/src/BaGet/Startup.cs +++ b/src/BaGet/Startup.cs @@ -69,6 +69,7 @@ private void ConfigureBaGetApplication(BaGetApplication app) app.AddAwsS3Storage(); app.AddAzureBlobStorage(); app.AddGoogleCloudStorage(); + app.AddMySqlStorage(); // Add search providers. app.AddAzureSearch();