using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; namespace BTCPayServer.Data { public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> { public ApplicationDbContext CreateDbContext(string[] args) { var builder = new DbContextOptionsBuilder<ApplicationDbContext>(); builder.UseSqlite("Data Source=temp.db"); return new ApplicationDbContext(builder.Options, true); } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { private readonly bool _designTime; public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, bool designTime = false) : base(options) { _designTime = designTime; } #nullable enable public async Task<string?> GetMigrationState() { return (await Settings.FromSqlRaw("SELECT \"Id\", \"Value\" FROM \"Settings\" WHERE \"Id\"='MigrationData'").AsNoTracking().FirstOrDefaultAsync())?.Value; } #nullable restore public DbSet<AddressInvoiceData> AddressInvoices { get; set; } public DbSet<APIKeyData> ApiKeys { get; set; } public DbSet<AppData> Apps { get; set; } public DbSet<CustodianAccountData> CustodianAccount { get; set; } public DbSet<StoredFile> Files { get; set; } public DbSet<InvoiceEventData> InvoiceEvents { get; set; } public DbSet<InvoiceSearchData> InvoiceSearches { get; set; } public DbSet<InvoiceWebhookDeliveryData> InvoiceWebhookDeliveries { get; set; } public DbSet<InvoiceData> Invoices { get; set; } public DbSet<NotificationData> Notifications { get; set; } public DbSet<OffchainTransactionData> OffchainTransactions { get; set; } public DbSet<PairedSINData> PairedSINData { get; set; } public DbSet<PairingCodeData> PairingCodes { get; set; } public DbSet<PayjoinLock> PayjoinLocks { get; set; } public DbSet<PaymentRequestData> PaymentRequests { get; set; } public DbSet<PaymentData> Payments { get; set; } public DbSet<PayoutData> Payouts { get; set; } public DbSet<PendingInvoiceData> PendingInvoices { get; set; } public DbSet<PlannedTransaction> PlannedTransactions { get; set; } public DbSet<PullPaymentData> PullPayments { get; set; } public DbSet<RefundData> Refunds { get; set; } public DbSet<SettingData> Settings { get; set; } public DbSet<StoreSettingData> StoreSettings { get; set; } public DbSet<StoreWebhookData> StoreWebhooks { get; set; } public DbSet<StoreData> Stores { get; set; } public DbSet<U2FDevice> U2FDevices { get; set; } public DbSet<Fido2Credential> Fido2Credentials { get; set; } public DbSet<UserStore> UserStore { get; set; } public DbSet<StoreRole> StoreRoles { get; set; } [Obsolete] public DbSet<WalletData> Wallets { get; set; } public DbSet<WalletObjectData> WalletObjects { get; set; } public DbSet<WalletObjectLinkData> WalletObjectLinks { get; set; } [Obsolete] public DbSet<WalletTransactionData> WalletTransactions { get; set; } public DbSet<WebhookDeliveryData> WebhookDeliveries { get; set; } public DbSet<WebhookData> Webhooks { get; set; } public DbSet<LightningAddressData> LightningAddresses { get; set; } public DbSet<PayoutProcessorData> PayoutProcessors { get; set; } public DbSet<FormData> Forms { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any(); if (!isConfigured) optionsBuilder.UseSqlite("Data Source=temp.db"); } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // some of the data models don't have OnModelCreating for now, commenting them ApplicationUser.OnModelCreating(builder, Database); AddressInvoiceData.OnModelCreating(builder); APIKeyData.OnModelCreating(builder, Database); AppData.OnModelCreating(builder); CustodianAccountData.OnModelCreating(builder, Database); //StoredFile.OnModelCreating(builder); InvoiceEventData.OnModelCreating(builder); InvoiceSearchData.OnModelCreating(builder); InvoiceWebhookDeliveryData.OnModelCreating(builder); InvoiceData.OnModelCreating(builder, Database); NotificationData.OnModelCreating(builder, Database); //OffchainTransactionData.OnModelCreating(builder); BTCPayServer.Data.PairedSINData.OnModelCreating(builder); PairingCodeData.OnModelCreating(builder); //PayjoinLock.OnModelCreating(builder); PaymentRequestData.OnModelCreating(builder, Database); PaymentData.OnModelCreating(builder, Database); PayoutData.OnModelCreating(builder); PendingInvoiceData.OnModelCreating(builder); //PlannedTransaction.OnModelCreating(builder); PullPaymentData.OnModelCreating(builder); RefundData.OnModelCreating(builder); SettingData.OnModelCreating(builder, Database); StoreSettingData.OnModelCreating(builder, Database); StoreWebhookData.OnModelCreating(builder); StoreData.OnModelCreating(builder, Database); U2FDevice.OnModelCreating(builder); Fido2Credential.OnModelCreating(builder, Database); BTCPayServer.Data.UserStore.OnModelCreating(builder); //WalletData.OnModelCreating(builder); WalletObjectData.OnModelCreating(builder, Database); WalletObjectLinkData.OnModelCreating(builder, Database); #pragma warning disable CS0612 // Type or member is obsolete WalletTransactionData.OnModelCreating(builder); #pragma warning restore CS0612 // Type or member is obsolete WebhookDeliveryData.OnModelCreating(builder, Database); LightningAddressData.OnModelCreating(builder, Database); PayoutProcessorData.OnModelCreating(builder, Database); WebhookData.OnModelCreating(builder, Database); FormData.OnModelCreating(builder, Database); StoreRole.OnModelCreating(builder, Database); if (Database.IsSqlite() && !_designTime) { // SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations // here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations // To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset // use the DateTimeOffsetToBinaryConverter // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754 // This only supports millisecond precision, but should be sufficient for most use cases. foreach (var entityType in builder.Model.GetEntityTypes()) { var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset)); foreach (var property in properties) { builder .Entity(entityType.Name) .Property(property.Name) .HasConversion(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.DateTimeOffsetToBinaryConverter()); } } } } } }