diff --git a/BTCPayServer.Data/Data/PaymentRequestData.Migration.cs b/BTCPayServer.Data/Data/PaymentRequestData.Migration.cs new file mode 100644 index 000000000..01319150c --- /dev/null +++ b/BTCPayServer.Data/Data/PaymentRequestData.Migration.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Data +{ + public partial class PaymentRequestData : MigrationInterceptor.IHasMigration + { + [NotMapped] + public bool Migrated { get; set; } + + public bool TryMigrate() + { +#pragma warning disable CS0618 // Type or member is obsolete + if (Blob is null && Blob2 is not null) + return false; + if (Blob2 is null) + { + Blob2 = Blob is not (null or { Length: 0 }) ? MigrationExtensions.Unzip(Blob) : "{}"; + Blob2 = MigrationExtensions.SanitizeJSON(Blob2); + } + Blob = null; +#pragma warning restore CS0618 // Type or member is obsolete + var jobj = JObject.Parse(Blob2); + // Fixup some legacy payment requests + if (jobj["expiryDate"].Type == JTokenType.Date) + { + jobj["expiryDate"] = new JValue(NBitcoin.Utils.DateTimeToUnixTime(jobj["expiryDate"].Value())); + Blob2 = jobj.ToString(Newtonsoft.Json.Formatting.None); + } + return true; + } + } +} diff --git a/BTCPayServer.Data/Data/PaymentRequestData.cs b/BTCPayServer.Data/Data/PaymentRequestData.cs index 4abf3846d..588e64f5b 100644 --- a/BTCPayServer.Data/Data/PaymentRequestData.cs +++ b/BTCPayServer.Data/Data/PaymentRequestData.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; namespace BTCPayServer.Data { - public class PaymentRequestData : IHasBlobUntyped + public partial class PaymentRequestData : IHasBlobUntyped { public string Id { get; set; } public DateTimeOffset Created { get; set; } diff --git a/BTCPayServer/Data/PaymentRequestDataExtensions.cs b/BTCPayServer/Data/PaymentRequestDataExtensions.cs index d10db8bb7..e5ceb7cb4 100644 --- a/BTCPayServer/Data/PaymentRequestDataExtensions.cs +++ b/BTCPayServer/Data/PaymentRequestDataExtensions.cs @@ -9,26 +9,7 @@ namespace BTCPayServer.Data { public static PaymentRequestBaseData GetBlob(this PaymentRequestData paymentRequestData) { - if (paymentRequestData.Blob2 is not null) - { - return paymentRequestData.HasTypedBlob().GetBlob(); - } -#pragma warning disable CS0618 // Type or member is obsolete - else if (paymentRequestData.Blob is not null) - { - return ParseBlob(paymentRequestData.Blob); - } -#pragma warning restore CS0618 // Type or member is obsolete - return new PaymentRequestBaseData(); - } - - static PaymentRequestBaseData ParseBlob(byte[] blob) - { - var jobj = JObject.Parse(ZipUtils.Unzip(blob)); - // Fixup some legacy payment requests - if (jobj["expiryDate"].Type == JTokenType.Date) - jobj["expiryDate"] = new JValue(NBitcoin.Utils.DateTimeToUnixTime(jobj["expiryDate"].Value())); - return jobj.ToObject(); + return paymentRequestData.HasTypedBlob().GetBlob() ?? new PaymentRequestBaseData(); } public static void SetBlob(this PaymentRequestData paymentRequestData, PaymentRequestBaseData blob) diff --git a/BTCPayServer/HostedServices/PaymentRequestsMigratorHostedService.cs b/BTCPayServer/HostedServices/PaymentRequestsMigratorHostedService.cs new file mode 100644 index 000000000..1d7d6a771 --- /dev/null +++ b/BTCPayServer/HostedServices/PaymentRequestsMigratorHostedService.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Abstractions.Contracts; +using BTCPayServer.Data; +using BTCPayServer.Migrations; +using BTCPayServer.Services.Invoices; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace BTCPayServer.HostedServices +{ + public class PaymentRequestsMigratorHostedService : BlobMigratorHostedService + { + public PaymentRequestsMigratorHostedService( + ILogger logs, + ISettingsRepository settingsRepository, + ApplicationDbContextFactory applicationDbContextFactory) : base(logs, settingsRepository, applicationDbContextFactory) + { + } + public override string SettingsKey => "PaymentRequestsMigration"; + + protected override IQueryable GetQuery(ApplicationDbContext ctx, DateTimeOffset? progress) + { +#pragma warning disable CS0618 // Type or member is obsolete + var query = progress is DateTimeOffset last2 ? + ctx.PaymentRequests.Where(i => i.Created < last2 && !(i.Blob == null && i.Blob2 != null)) : + ctx.PaymentRequests.Where(i => !(i.Blob == null && i.Blob2 != null)); + return query.OrderByDescending(i => i.Created); +#pragma warning restore CS0618 // Type or member is obsolete + } + + protected override async Task PostMigrationCleanup(ApplicationDbContext ctx, CancellationToken cancellationToken) + { + Logs.LogInformation("Post-migration VACUUM (FULL, ANALYZE)"); + await ctx.Database.ExecuteSqlRawAsync("VACUUM (FULL, ANALYZE) \"PaymentRequests\"", cancellationToken); + Logs.LogInformation("Post-migration VACUUM (FULL, ANALYZE) finished"); + } + + protected override DateTimeOffset ProcessEntities(ApplicationDbContext ctx, List entities) + { + // The PaymentRequestData.Migrate() is automatically called by EF. + // But Modified isn't set as it happens before the ctx is bound to the entity. + foreach (var entity in entities) + { + ctx.PaymentRequests.Entry(entity).State = EntityState.Modified; + } + return entities[^1].Created; + } + + protected override Task Reindex(ApplicationDbContext ctx, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index d9d454224..84b62bdbd 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -578,6 +578,9 @@ o.GetRequiredService>().ToDictionary(o => o.P services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(o => o.GetRequiredService()); + // Broken // Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM")));