mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 22:11:48 +01:00
Remove only dependency on Dbriize (TextSearch in new invoice column) (#2029)
* Remove only dependency on Dbriize (TextSearch in new invoice column) * Switch to table for invoice text search * Adding missing using after rebase * Removing database migration in preparation for refresh * Database Migration: Adding InvoiceSearchData * Refactoring InvoicesRepository to make AddToTextSearch static and non-async Operation as async is too expensive for simple filtering and AddRange * Renaming InvoiceQuery property to Take More inline with what property does by convention, Take is used in conjuction with Skip * Refactoring SettingsRepository so update of settings can happen in another context * Adding DbMigrationsHostedService that performs long running data migrations * Commenting special placing of MigrationStartupTask * Simplifying code and leaving comment on expected flow * Resolving problems after merge * Database Migration: Refreshing database migration, ensuring no unintended changes on ModelSnapshot Co-authored-by: rockstardev <rockstardev@users.noreply.github.com> Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
a6ee64ea63
commit
39b5462809
13 changed files with 324 additions and 110 deletions
|
@ -67,6 +67,7 @@ namespace BTCPayServer.Data
|
||||||
public DbSet<WebhookData> Webhooks { get; set; }
|
public DbSet<WebhookData> Webhooks { get; set; }
|
||||||
public DbSet<WebhookDeliveryData> WebhookDeliveries { get; set; }
|
public DbSet<WebhookDeliveryData> WebhookDeliveries { get; set; }
|
||||||
public DbSet<InvoiceWebhookDeliveryData> InvoiceWebhookDeliveries { get; set; }
|
public DbSet<InvoiceWebhookDeliveryData> InvoiceWebhookDeliveries { get; set; }
|
||||||
|
public DbSet<InvoiceSearchData> InvoiceSearchDatas { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
@ -81,6 +82,7 @@ namespace BTCPayServer.Data
|
||||||
Data.UserStore.OnModelCreating(builder);
|
Data.UserStore.OnModelCreating(builder);
|
||||||
NotificationData.OnModelCreating(builder);
|
NotificationData.OnModelCreating(builder);
|
||||||
InvoiceData.OnModelCreating(builder);
|
InvoiceData.OnModelCreating(builder);
|
||||||
|
InvoiceSearchData.OnModelCreating(builder);
|
||||||
PaymentData.OnModelCreating(builder);
|
PaymentData.OnModelCreating(builder);
|
||||||
Data.UserStore.OnModelCreating(builder);
|
Data.UserStore.OnModelCreating(builder);
|
||||||
APIKeyData.OnModelCreating(builder);
|
APIKeyData.OnModelCreating(builder);
|
||||||
|
|
|
@ -75,6 +75,7 @@ namespace BTCPayServer.Data
|
||||||
}
|
}
|
||||||
public bool Archived { get; set; }
|
public bool Archived { get; set; }
|
||||||
public List<PendingInvoiceData> PendingInvoices { get; set; }
|
public List<PendingInvoiceData> PendingInvoices { get; set; }
|
||||||
|
public List<InvoiceSearchData> InvoiceSearchData { get; set; }
|
||||||
public List<RefundData> Refunds { get; set; }
|
public List<RefundData> Refunds { get; set; }
|
||||||
public string CurrentRefundId { get; set; }
|
public string CurrentRefundId { get; set; }
|
||||||
[ForeignKey("Id,CurrentRefundId")]
|
[ForeignKey("Id,CurrentRefundId")]
|
||||||
|
|
38
BTCPayServer.Data/Data/InvoiceSearchData.cs
Normal file
38
BTCPayServer.Data/Data/InvoiceSearchData.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Data
|
||||||
|
{
|
||||||
|
public class InvoiceSearchData
|
||||||
|
{
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(InvoiceData))]
|
||||||
|
public string InvoiceDataId { get; set; }
|
||||||
|
public InvoiceData InvoiceData { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
internal static void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Entity<InvoiceSearchData>()
|
||||||
|
.HasOne(o => o.InvoiceData)
|
||||||
|
.WithMany(a => a.InvoiceSearchData)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.Entity<InvoiceSearchData>()
|
||||||
|
.HasIndex(data => data.Value);
|
||||||
|
|
||||||
|
builder.Entity<InvoiceSearchData>()
|
||||||
|
.Property(a => a.Id)
|
||||||
|
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
|
||||||
|
.HasAnnotation("MySql:ValueGeneratedOnAdd", true)
|
||||||
|
.HasAnnotation("Sqlite:Autoincrement", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20201227165824_AdddingInvoiceSearchData")]
|
||||||
|
public partial class AdddingInvoiceSearchData : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "InvoiceSearchDatas",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(nullable: false)
|
||||||
|
// manually added
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
|
||||||
|
.Annotation("MySql:ValueGeneratedOnAdd", true)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
// eof manually added
|
||||||
|
InvoiceDataId = table.Column<string>(nullable: true),
|
||||||
|
Value = table.Column<string>(nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_InvoiceSearchDatas", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_InvoiceSearchDatas_Invoices_InvoiceDataId",
|
||||||
|
column: x => x.InvoiceDataId,
|
||||||
|
principalTable: "Invoices",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_InvoiceSearchDatas_InvoiceDataId",
|
||||||
|
table: "InvoiceSearchDatas",
|
||||||
|
column: "InvoiceDataId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_InvoiceSearchDatas_Value",
|
||||||
|
table: "InvoiceSearchDatas",
|
||||||
|
column: "Value");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "InvoiceSearchDatas");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ using BTCPayServer.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
namespace BTCPayServer.Migrations
|
namespace BTCPayServer.Migrations
|
||||||
{
|
{
|
||||||
|
@ -259,6 +260,30 @@ namespace BTCPayServer.Migrations
|
||||||
b.ToTable("InvoiceEvents");
|
b.ToTable("InvoiceEvents");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("BTCPayServer.Data.InvoiceSearchData", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasAnnotation("MySql:ValueGeneratedOnAdd", true)
|
||||||
|
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
|
||||||
|
.HasAnnotation("Sqlite:Autoincrement", true);
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceDataId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("InvoiceDataId");
|
||||||
|
|
||||||
|
b.HasIndex("Value");
|
||||||
|
|
||||||
|
b.ToTable("InvoiceSearchDatas");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("InvoiceId")
|
b.Property<string>("InvoiceId")
|
||||||
|
@ -963,6 +988,14 @@ namespace BTCPayServer.Migrations
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("BTCPayServer.Data.InvoiceSearchData", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||||
|
.WithMany("InvoiceSearchData")
|
||||||
|
.HasForeignKey("InvoiceDataId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
modelBuilder.Entity("BTCPayServer.Data.InvoiceWebhookDeliveryData", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("BTCPayServer.Data.WebhookDeliveryData", "Delivery")
|
b.HasOne("BTCPayServer.Data.WebhookDeliveryData", "Delivery")
|
||||||
|
|
|
@ -69,7 +69,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
var query = new InvoiceQuery()
|
var query = new InvoiceQuery()
|
||||||
{
|
{
|
||||||
Count = limit,
|
Take = limit,
|
||||||
Skip = offset,
|
Skip = offset,
|
||||||
EndDate = dateEnd,
|
EndDate = dateEnd,
|
||||||
StartDate = dateStart,
|
StartDate = dateStart,
|
||||||
|
|
|
@ -705,7 +705,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
|
InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
|
||||||
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
|
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
|
||||||
invoiceQuery.Count = model.Count;
|
invoiceQuery.Take = model.Count;
|
||||||
invoiceQuery.Skip = model.Skip;
|
invoiceQuery.Skip = model.Skip;
|
||||||
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
||||||
|
|
||||||
|
@ -759,7 +759,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
|
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
|
||||||
invoiceQuery.Skip = 0;
|
invoiceQuery.Skip = 0;
|
||||||
invoiceQuery.Count = int.MaxValue;
|
invoiceQuery.Take = int.MaxValue;
|
||||||
var invoices = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
var invoices = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
||||||
var res = model.Process(invoices, format);
|
var res = model.Process(invoices, format);
|
||||||
|
|
||||||
|
|
107
BTCPayServer/HostedServices/DbMigrationsHostedService.cs
Normal file
107
BTCPayServer/HostedServices/DbMigrationsHostedService.cs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
|
|
||||||
|
namespace BTCPayServer.HostedServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// In charge of all long running db migrations that we can't execute on startup in MigrationStartupTask
|
||||||
|
/// </summary>
|
||||||
|
public class DbMigrationsHostedService : BaseAsyncService
|
||||||
|
{
|
||||||
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
|
private readonly SettingsRepository _settingsRepository;
|
||||||
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
|
|
||||||
|
public DbMigrationsHostedService(InvoiceRepository invoiceRepository, SettingsRepository settingsRepository, ApplicationDbContextFactory dbContextFactory)
|
||||||
|
{
|
||||||
|
_invoiceRepository = invoiceRepository;
|
||||||
|
_settingsRepository = settingsRepository;
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal override Task[] InitializeTasks()
|
||||||
|
{
|
||||||
|
return new Task[] { ProcessMigration() };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task ProcessMigration()
|
||||||
|
{
|
||||||
|
var settings = await _settingsRepository.GetSettingAsync<MigrationSettings>();
|
||||||
|
if (settings.MigratedInvoiceTextSearchPages != int.MaxValue)
|
||||||
|
{
|
||||||
|
await MigratedInvoiceTextSearchToDb(settings.MigratedInvoiceTextSearchPages.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh settings since these operations may run for very long time
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MigratedInvoiceTextSearchToDb(int startFromPage)
|
||||||
|
{
|
||||||
|
using var ctx = _dbContextFactory.CreateContext();
|
||||||
|
|
||||||
|
var invoiceQuery = new InvoiceQuery { IncludeArchived = true };
|
||||||
|
var totalCount = await _invoiceRepository.GetInvoicesTotal(invoiceQuery);
|
||||||
|
const int PAGE_SIZE = 1000;
|
||||||
|
var totalPages = Math.Ceiling(totalCount * 1.0m / PAGE_SIZE);
|
||||||
|
for (int i = startFromPage; i < totalPages; i++)
|
||||||
|
{
|
||||||
|
invoiceQuery.Skip = i * PAGE_SIZE;
|
||||||
|
invoiceQuery.Take = PAGE_SIZE;
|
||||||
|
var invoices = await _invoiceRepository.GetInvoices(invoiceQuery);
|
||||||
|
|
||||||
|
foreach (var invoice in invoices)
|
||||||
|
{
|
||||||
|
var textSearch = new List<string>();
|
||||||
|
|
||||||
|
// recreating different textSearch.Adds that were previously in DBriize
|
||||||
|
foreach (var paymentMethod in invoice.GetPaymentMethods())
|
||||||
|
{
|
||||||
|
if (paymentMethod.Network != null)
|
||||||
|
{
|
||||||
|
var paymentDestination = paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();
|
||||||
|
textSearch.Add(paymentDestination);
|
||||||
|
textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
textSearch.Add(invoice.Id);
|
||||||
|
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
||||||
|
textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
|
||||||
|
textSearch.Add(invoice.Metadata.OrderId);
|
||||||
|
textSearch.Add(InvoiceRepository.ToJsonString(invoice.Metadata, null));
|
||||||
|
textSearch.Add(invoice.StoreId);
|
||||||
|
textSearch.Add(invoice.Metadata.BuyerEmail);
|
||||||
|
//
|
||||||
|
textSearch.Add(invoice.RefundMail);
|
||||||
|
// TODO: Are there more things to cache? PaymentData?
|
||||||
|
|
||||||
|
InvoiceRepository.AddToTextSearch(ctx,
|
||||||
|
new InvoiceData { Id = invoice.Id, InvoiceSearchData = new List<InvoiceSearchData>() },
|
||||||
|
textSearch.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = await _settingsRepository.GetSettingAsync<MigrationSettings>();
|
||||||
|
if (i + 1 < totalPages)
|
||||||
|
{
|
||||||
|
settings.MigratedInvoiceTextSearchPages = i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// during final pass we set int.MaxValue so migration doesn't run again
|
||||||
|
settings.MigratedInvoiceTextSearchPages = int.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this call triggers update; we're sure that MigrationSettings is already initialized in db
|
||||||
|
// because of logic executed in MigrationStartupTask.cs
|
||||||
|
_settingsRepository.UpdateSettingInContext(ctx, settings);
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,16 +97,15 @@ namespace BTCPayServer.Hosting
|
||||||
services.TryAddSingleton<InvoicePaymentNotification>();
|
services.TryAddSingleton<InvoicePaymentNotification>();
|
||||||
services.TryAddSingleton<BTCPayServerOptions>(o =>
|
services.TryAddSingleton<BTCPayServerOptions>(o =>
|
||||||
o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
|
||||||
|
// Don't move this StartupTask, we depend on it being right here
|
||||||
services.AddStartupTask<MigrationStartupTask>();
|
services.AddStartupTask<MigrationStartupTask>();
|
||||||
|
//
|
||||||
services.AddStartupTask<BlockExplorerLinkStartupTask>();
|
services.AddStartupTask<BlockExplorerLinkStartupTask>();
|
||||||
services.TryAddSingleton<InvoiceRepository>(o =>
|
services.TryAddSingleton<InvoiceRepository>(o =>
|
||||||
{
|
{
|
||||||
var datadirs = o.GetRequiredService<DataDirectories>();
|
var datadirs = o.GetRequiredService<DataDirectories>();
|
||||||
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
|
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
|
||||||
var dbpath = Path.Combine(datadirs.DataDir, "InvoiceDB");
|
return new InvoiceRepository(dbContext, o.GetRequiredService<BTCPayNetworkProvider>(), o.GetService<EventAggregator>());
|
||||||
if (!Directory.Exists(dbpath))
|
|
||||||
Directory.CreateDirectory(dbpath);
|
|
||||||
return new InvoiceRepository(dbContext, dbpath, o.GetRequiredService<BTCPayNetworkProvider>(), o.GetService<EventAggregator>());
|
|
||||||
});
|
});
|
||||||
services.AddSingleton<BTCPayServerEnvironment>();
|
services.AddSingleton<BTCPayServerEnvironment>();
|
||||||
services.TryAddSingleton<TokenRepository>();
|
services.TryAddSingleton<TokenRepository>();
|
||||||
|
@ -264,6 +263,8 @@ namespace BTCPayServer.Hosting
|
||||||
services.AddSingleton<INotificationHandler, InvoiceEventNotification.Handler>();
|
services.AddSingleton<INotificationHandler, InvoiceEventNotification.Handler>();
|
||||||
services.AddSingleton<INotificationHandler, PayoutNotification.Handler>();
|
services.AddSingleton<INotificationHandler, PayoutNotification.Handler>();
|
||||||
|
|
||||||
|
services.AddSingleton<IHostedService, DbMigrationsHostedService>();
|
||||||
|
|
||||||
services.AddShopify();
|
services.AddShopify();
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
services.AddSingleton<INotificationHandler, JunkNotification.Handler>();
|
services.AddSingleton<INotificationHandler, JunkNotification.Handler>();
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Contracts;
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Configuration;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
|
using DBriize;
|
||||||
|
using DBriize.Utils;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
|
@ -20,18 +27,21 @@ namespace BTCPayServer.Hosting
|
||||||
private readonly StoreRepository _StoreRepository;
|
private readonly StoreRepository _StoreRepository;
|
||||||
private readonly BTCPayNetworkProvider _NetworkProvider;
|
private readonly BTCPayNetworkProvider _NetworkProvider;
|
||||||
private readonly SettingsRepository _Settings;
|
private readonly SettingsRepository _Settings;
|
||||||
|
private readonly BTCPayServerOptions _btcPayServerOptions;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
public MigrationStartupTask(
|
public MigrationStartupTask(
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
ApplicationDbContextFactory dbContextFactory,
|
ApplicationDbContextFactory dbContextFactory,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
SettingsRepository settingsRepository)
|
SettingsRepository settingsRepository,
|
||||||
|
BTCPayServerOptions btcPayServerOptions)
|
||||||
{
|
{
|
||||||
_DBContextFactory = dbContextFactory;
|
_DBContextFactory = dbContextFactory;
|
||||||
_StoreRepository = storeRepository;
|
_StoreRepository = storeRepository;
|
||||||
_NetworkProvider = networkProvider;
|
_NetworkProvider = networkProvider;
|
||||||
_Settings = settingsRepository;
|
_Settings = settingsRepository;
|
||||||
|
_btcPayServerOptions = btcPayServerOptions;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||||
|
|
|
@ -2,16 +2,13 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
using BTCPayServer.Models.InvoicingModels;
|
using BTCPayServer.Models.InvoicingModels;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using DBriize;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
@ -22,7 +19,7 @@ using InvoiceData = BTCPayServer.Data.InvoiceData;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
public class InvoiceRepository : IDisposable
|
public class InvoiceRepository
|
||||||
{
|
{
|
||||||
static JsonSerializerSettings DefaultSerializerSettings;
|
static JsonSerializerSettings DefaultSerializerSettings;
|
||||||
static InvoiceRepository()
|
static InvoiceRepository()
|
||||||
|
@ -31,31 +28,13 @@ namespace BTCPayServer.Services.Invoices
|
||||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly DBriizeEngine _Engine;
|
|
||||||
public DBriizeEngine Engine
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _Engine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ApplicationDbContextFactory _ContextFactory;
|
private readonly ApplicationDbContextFactory _ContextFactory;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly BTCPayNetworkProvider _Networks;
|
private readonly BTCPayNetworkProvider _Networks;
|
||||||
private readonly CustomThreadPool _IndexerThread;
|
|
||||||
|
|
||||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath,
|
public InvoiceRepository(ApplicationDbContextFactory contextFactory,
|
||||||
BTCPayNetworkProvider networks, EventAggregator eventAggregator)
|
BTCPayNetworkProvider networks, EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
int retryCount = 0;
|
|
||||||
retry:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_Engine = new DBriizeEngine(dbreezePath);
|
|
||||||
}
|
|
||||||
catch when (retryCount++ < 5) { goto retry; }
|
|
||||||
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
|
|
||||||
_ContextFactory = contextFactory;
|
_ContextFactory = contextFactory;
|
||||||
_Networks = networks;
|
_Networks = networks;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
|
@ -148,7 +127,7 @@ retry:
|
||||||
if (invoiceData.CustomerEmail == null && data.Email != null)
|
if (invoiceData.CustomerEmail == null && data.Email != null)
|
||||||
{
|
{
|
||||||
invoiceData.CustomerEmail = data.Email;
|
invoiceData.CustomerEmail = data.Email;
|
||||||
AddToTextSearch(invoiceId, invoiceData.CustomerEmail);
|
AddToTextSearch(ctx, invoiceData, invoiceData.CustomerEmail);
|
||||||
}
|
}
|
||||||
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +149,7 @@ retry:
|
||||||
|
|
||||||
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice)
|
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
List<string> textSearch = new List<string>();
|
var textSearch = new List<string>();
|
||||||
invoice = Clone(invoice);
|
invoice = Clone(invoice);
|
||||||
invoice.Networks = _Networks;
|
invoice.Networks = _Networks;
|
||||||
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
||||||
|
@ -180,7 +159,7 @@ retry:
|
||||||
invoice.StoreId = storeId;
|
invoice.StoreId = storeId;
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
context.Invoices.Add(new Data.InvoiceData()
|
var invoiceData = new Data.InvoiceData()
|
||||||
{
|
{
|
||||||
StoreDataId = storeId,
|
StoreDataId = storeId,
|
||||||
Id = invoice.Id,
|
Id = invoice.Id,
|
||||||
|
@ -193,7 +172,9 @@ retry:
|
||||||
ItemCode = invoice.Metadata.ItemCode,
|
ItemCode = invoice.Metadata.ItemCode,
|
||||||
CustomerEmail = invoice.RefundMail,
|
CustomerEmail = invoice.RefundMail,
|
||||||
Archived = false
|
Archived = false
|
||||||
});
|
};
|
||||||
|
await context.Invoices.AddAsync(invoiceData);
|
||||||
|
|
||||||
|
|
||||||
foreach (var paymentMethod in invoice.GetPaymentMethods())
|
foreach (var paymentMethod in invoice.GetPaymentMethods())
|
||||||
{
|
{
|
||||||
|
@ -202,13 +183,13 @@ retry:
|
||||||
var paymentDestination = paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();
|
var paymentDestination = paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();
|
||||||
|
|
||||||
string address = GetDestination(paymentMethod);
|
string address = GetDestination(paymentMethod);
|
||||||
context.AddressInvoices.Add(new AddressInvoiceData()
|
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
||||||
{
|
{
|
||||||
InvoiceDataId = invoice.Id,
|
InvoiceDataId = invoice.Id,
|
||||||
CreatedTime = DateTimeOffset.UtcNow,
|
CreatedTime = DateTimeOffset.UtcNow,
|
||||||
}.Set(address, paymentMethod.GetId()));
|
}.Set(address, paymentMethod.GetId()));
|
||||||
|
|
||||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
|
||||||
{
|
{
|
||||||
InvoiceDataId = invoice.Id,
|
InvoiceDataId = invoice.Id,
|
||||||
Assigned = DateTimeOffset.UtcNow
|
Assigned = DateTimeOffset.UtcNow
|
||||||
|
@ -216,18 +197,21 @@ retry:
|
||||||
textSearch.Add(paymentDestination);
|
textSearch.Add(paymentDestination);
|
||||||
textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
|
textSearch.Add(paymentMethod.Calculate().TotalDue.ToString());
|
||||||
}
|
}
|
||||||
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id });
|
await context.PendingInvoices.AddAsync(new PendingInvoiceData() { Id = invoice.Id });
|
||||||
|
|
||||||
|
textSearch.Add(invoice.Id);
|
||||||
|
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
||||||
|
textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
|
||||||
|
textSearch.Add(invoice.Metadata.OrderId);
|
||||||
|
textSearch.Add(ToJsonString(invoice.Metadata, null));
|
||||||
|
textSearch.Add(invoice.StoreId);
|
||||||
|
textSearch.Add(invoice.Metadata.BuyerEmail);
|
||||||
|
AddToTextSearch(context, invoiceData, textSearch.ToArray());
|
||||||
|
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
textSearch.Add(invoice.Id);
|
|
||||||
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
|
||||||
textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
|
|
||||||
textSearch.Add(invoice.Metadata.OrderId);
|
|
||||||
textSearch.Add(ToString(invoice.Metadata, null));
|
|
||||||
textSearch.Add(invoice.StoreId);
|
|
||||||
textSearch.Add(invoice.Metadata.BuyerEmail);
|
|
||||||
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
|
||||||
return invoice;
|
return invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,10 +279,10 @@ retry:
|
||||||
invoice.Blob = ToBytes(invoiceEntity, network);
|
invoice.Blob = ToBytes(invoiceEntity, network);
|
||||||
|
|
||||||
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
await context.AddressInvoices.AddAsync(new AddressInvoiceData()
|
||||||
{
|
{
|
||||||
InvoiceDataId = invoiceId,
|
InvoiceDataId = invoiceId,
|
||||||
CreatedTime = DateTimeOffset.UtcNow
|
CreatedTime = DateTimeOffset.UtcNow
|
||||||
}
|
}
|
||||||
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
|
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
|
||||||
await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
|
await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
|
||||||
{
|
{
|
||||||
|
@ -306,8 +290,8 @@ retry:
|
||||||
Assigned = DateTimeOffset.UtcNow
|
Assigned = DateTimeOffset.UtcNow
|
||||||
}.SetAddress(paymentMethodDetails.GetPaymentDestination(), network.CryptoCode));
|
}.SetAddress(paymentMethodDetails.GetPaymentDestination(), network.CryptoCode));
|
||||||
|
|
||||||
|
AddToTextSearch(context, invoice, paymentMethodDetails.GetPaymentDestination());
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
AddToTextSearch(invoice.Id, paymentMethodDetails.GetPaymentDestination());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +341,7 @@ retry:
|
||||||
data.UnAssigned == null);
|
data.UnAssigned == null);
|
||||||
foreach (var historicalAddressInvoiceData in addresses)
|
foreach (var historicalAddressInvoiceData in addresses)
|
||||||
{
|
{
|
||||||
historicalAddressInvoiceData.UnAssigned = DateTimeOffset.UtcNow;
|
historicalAddressInvoiceData.UnAssigned = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,29 +356,13 @@ retry:
|
||||||
catch (DbUpdateException) { } //Possibly, it was unassigned before
|
catch (DbUpdateException) { } //Possibly, it was unassigned before
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] SearchInvoice(string searchTerms)
|
public static void AddToTextSearch(ApplicationDbContext context, InvoiceData invoice, params string[] terms)
|
||||||
{
|
{
|
||||||
using (var tx = _Engine.GetTransaction())
|
var filteredTerms = terms.Where(t => !string.IsNullOrWhiteSpace(t)
|
||||||
{
|
&& (invoice.InvoiceSearchData == null || invoice.InvoiceSearchData.All(data => data.Value != t)))
|
||||||
var terms = searchTerms.Split(null);
|
.Distinct()
|
||||||
searchTerms = string.Join(' ', terms.Select(t => t.Length > 50 ? t.Substring(0, 50) : t).ToArray());
|
.Select(s => new InvoiceSearchData() { InvoiceDataId = invoice.Id, Value = s });
|
||||||
return tx.TextSearch("InvoiceSearch").Block(searchTerms)
|
context.AddRange(filteredTerms);
|
||||||
.GetDocumentIDs()
|
|
||||||
.Select(id => Encoders.Base58.EncodeData(id))
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddToTextSearch(string invoiceId, params string[] terms)
|
|
||||||
{
|
|
||||||
_IndexerThread.DoAsync(() =>
|
|
||||||
{
|
|
||||||
using (var tx = _Engine.GetTransaction())
|
|
||||||
{
|
|
||||||
tx.TextAppend("InvoiceSearch", Encoders.Base58.DecodeData(invoiceId), string.Join(" ", terms.Where(t => !string.IsNullOrWhiteSpace(t))));
|
|
||||||
tx.Commit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
|
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
|
||||||
|
@ -415,7 +383,8 @@ retry:
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var items = context.Invoices.Where(a => invoiceIds.Contains(a.Id));
|
var items = context.Invoices.Where(a => invoiceIds.Contains(a.Id));
|
||||||
if (items == null) {
|
if (items == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +392,7 @@ retry:
|
||||||
{
|
{
|
||||||
invoice.Archived = true;
|
invoice.Archived = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -441,7 +410,7 @@ retry:
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata)
|
public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
|
@ -451,7 +420,7 @@ retry:
|
||||||
StringComparison.InvariantCultureIgnoreCase)))
|
StringComparison.InvariantCultureIgnoreCase)))
|
||||||
return null;
|
return null;
|
||||||
var blob = invoiceData.GetBlob(_Networks);
|
var blob = invoiceData.GetBlob(_Networks);
|
||||||
blob.Metadata = InvoiceMetadata.FromJObject(metadata);
|
blob.Metadata = InvoiceMetadata.FromJObject(metadata);
|
||||||
invoiceData.Blob = ToBytes(blob);
|
invoiceData.Blob = ToBytes(blob);
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
return ToEntity(invoiceData);
|
return ToEntity(invoiceData);
|
||||||
|
@ -595,9 +564,11 @@ retry:
|
||||||
|
|
||||||
private IQueryable<Data.InvoiceData> GetInvoiceQuery(ApplicationDbContext context, InvoiceQuery queryObject)
|
private IQueryable<Data.InvoiceData> GetInvoiceQuery(ApplicationDbContext context, InvoiceQuery queryObject)
|
||||||
{
|
{
|
||||||
IQueryable<Data.InvoiceData> query = queryObject.UserId is null
|
IQueryable<Data.InvoiceData> query = queryObject.UserId is null
|
||||||
? context.Invoices
|
? context.Invoices
|
||||||
: context.UserStore.Where(u => u.ApplicationUserId == queryObject.UserId).SelectMany(c => c.StoreData.Invoices);
|
: context.UserStore
|
||||||
|
.Where(u => u.ApplicationUserId == queryObject.UserId)
|
||||||
|
.SelectMany(c => c.StoreData.Invoices);
|
||||||
|
|
||||||
if (!queryObject.IncludeArchived)
|
if (!queryObject.IncludeArchived)
|
||||||
{
|
{
|
||||||
|
@ -618,14 +589,9 @@ retry:
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(queryObject.TextSearch))
|
if (!string.IsNullOrEmpty(queryObject.TextSearch))
|
||||||
{
|
{
|
||||||
var ids = new HashSet<string>(SearchInvoice(queryObject.TextSearch)).ToArray();
|
#pragma warning disable CA1307 // Specify StringComparison
|
||||||
if (ids.Length == 0)
|
query = query.Where(i => i.InvoiceSearchData.Any(data => data.Value.StartsWith(queryObject.TextSearch)));
|
||||||
{
|
#pragma warning restore CA1307 // Specify StringComparison
|
||||||
// Hacky way to return an empty query object. The nice way is much too elaborate:
|
|
||||||
// https://stackoverflow.com/questions/33305495/how-to-return-empty-iqueryable-in-an-async-repository-method
|
|
||||||
return query.Where(x => false);
|
|
||||||
}
|
|
||||||
query = query.Where(i => ids.Contains(i.Id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryObject.StartDate != null)
|
if (queryObject.StartDate != null)
|
||||||
|
@ -668,8 +634,8 @@ retry:
|
||||||
if (queryObject.Skip != null)
|
if (queryObject.Skip != null)
|
||||||
query = query.Skip(queryObject.Skip.Value);
|
query = query.Skip(queryObject.Skip.Value);
|
||||||
|
|
||||||
if (queryObject.Count != null)
|
if (queryObject.Take != null)
|
||||||
query = query.Take(queryObject.Count.Value);
|
query = query.Take(queryObject.Take.Value);
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,12 +737,12 @@ retry:
|
||||||
|
|
||||||
await context.Payments.AddAsync(data);
|
await context.Payments.AddAsync(data);
|
||||||
|
|
||||||
|
AddToTextSearch(context, invoice, paymentData.GetSearchTerms());
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException) { return null; } // Already exists
|
catch (DbUpdateException) { return null; } // Already exists
|
||||||
AddToTextSearch(invoiceId, paymentData.GetSearchTerms());
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -802,12 +768,12 @@ retry:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ToBytes<T>(T obj, BTCPayNetworkBase network = null)
|
private static byte[] ToBytes<T>(T obj, BTCPayNetworkBase network = null)
|
||||||
{
|
{
|
||||||
return ZipUtils.Zip(ToString(obj, network));
|
return ZipUtils.Zip(ToJsonString(obj, network));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ToString<T>(T data, BTCPayNetworkBase network)
|
public static string ToJsonString<T>(T data, BTCPayNetworkBase network)
|
||||||
{
|
{
|
||||||
if (network == null)
|
if (network == null)
|
||||||
{
|
{
|
||||||
|
@ -815,14 +781,6 @@ retry:
|
||||||
}
|
}
|
||||||
return network.ToString(data);
|
return network.ToString(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_Engine != null)
|
|
||||||
_Engine.Dispose();
|
|
||||||
if (_IndexerThread != null)
|
|
||||||
_IndexerThread.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InvoiceQuery
|
public class InvoiceQuery
|
||||||
|
@ -854,7 +812,7 @@ retry:
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? Count
|
public int? Take
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,5 +13,8 @@ namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Done in DbMigrationsHostedService
|
||||||
|
public int? MigratedInvoiceTextSearchPages { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,17 +30,11 @@ namespace BTCPayServer.Services
|
||||||
return Deserialize<T>(data.Value);
|
return Deserialize<T>(data.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateSetting<T>(T obj, string name = null)
|
public async Task UpdateSetting<T>(T obj, string name = null)
|
||||||
{
|
{
|
||||||
name ??= obj.GetType().FullName;
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var settings = new SettingData();
|
var settings = UpdateSettingInContext<T>(ctx, obj, name);
|
||||||
settings.Id = name;
|
|
||||||
settings.Value = Serialize(obj);
|
|
||||||
ctx.Attach(settings);
|
|
||||||
ctx.Entry(settings).State = EntityState.Modified;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
|
@ -55,7 +49,19 @@ namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
Settings = obj
|
Settings = obj
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public SettingData UpdateSettingInContext<T>(ApplicationDbContext ctx, T obj, string name = null)
|
||||||
|
{
|
||||||
|
name ??= obj.GetType().FullName;
|
||||||
|
var settings = new SettingData();
|
||||||
|
settings.Id = name;
|
||||||
|
settings.Value = Serialize(obj);
|
||||||
|
|
||||||
|
ctx.Attach(settings);
|
||||||
|
ctx.Entry(settings).State = EntityState.Modified;
|
||||||
|
|
||||||
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private T Deserialize<T>(string value)
|
private T Deserialize<T>(string value)
|
||||||
|
|
Loading…
Add table
Reference in a new issue