1
0
mirror of https://github.com/bitwarden/server synced 2026-02-14 23:45:11 +00:00

[PM 30100][Server] Subscription Discount Database Infrastructure (#6936)

* Implement the detail Subscription Discount Database Infrastructure

* Change string to string list

* fix lint error

* Create all missing database object definition files

* Regenerate EF migrations with Designer files

The previous migrations were missing .Designer.cs files. This commit:
- Removes the incomplete migration files
- Regenerates all three provider migrations (MySQL, Postgres, SQLite) with proper Designer files
- Updates DatabaseContextModelSnapshot.cs for each provider

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix failing database

* Resolve the lint  warnings

* Resolve the database failure

* Fix the build Lint

* resolve the dbops reviews

* Add the default value

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cyprain-okeke
2026-02-06 18:24:26 +01:00
committed by GitHub
parent e3008ccb68
commit 67ba9bcca5
32 changed files with 12003 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
using System.Text.Json;
using Bit.Infrastructure.EntityFramework.Billing.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Bit.Infrastructure.EntityFramework.Billing.Configurations;
public class SubscriptionDiscountEntityTypeConfiguration : IEntityTypeConfiguration<SubscriptionDiscount>
{
public void Configure(EntityTypeBuilder<SubscriptionDiscount> builder)
{
builder
.Property(t => t.Id)
.ValueGeneratedNever();
builder
.HasIndex(sd => sd.StripeCouponId)
.IsUnique();
builder
.Property(sd => sd.StripeProductIds)
.HasConversion(
v => v == null ? null : JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => v == null ? null : JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null),
new ValueComparer<ICollection<string>?>(
(c1, c2) => (c1 == null && c2 == null) || (c1 != null && c2 != null && c1.SequenceEqual(c2)),
c => c == null ? 0 : c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c == null ? null : c.ToList()));
builder
.Property(sd => sd.PercentOff)
.HasPrecision(5, 2);
builder
.HasIndex(sd => new { sd.StartDate, sd.EndDate })
.IsClustered(false)
.HasDatabaseName("IX_SubscriptionDiscount_DateRange");
builder.ToTable(nameof(SubscriptionDiscount));
}
}

View File

@@ -0,0 +1,18 @@
#nullable enable
using AutoMapper;
namespace Bit.Infrastructure.EntityFramework.Billing.Models;
// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
public class SubscriptionDiscount : Core.Billing.Subscriptions.Entities.SubscriptionDiscount
{
}
public class SubscriptionDiscountMapperProfile : Profile
{
public SubscriptionDiscountMapperProfile()
{
CreateMap<Core.Billing.Subscriptions.Entities.SubscriptionDiscount, SubscriptionDiscount>().ReverseMap();
}
}

View File

@@ -0,0 +1,51 @@
using AutoMapper;
using Bit.Core.Billing.Subscriptions.Entities;
using Bit.Core.Billing.Subscriptions.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using EFSubscriptionDiscount = Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount;
namespace Bit.Infrastructure.EntityFramework.Billing.Repositories;
public class SubscriptionDiscountRepository(
IMapper mapper,
IServiceScopeFactory serviceScopeFactory)
: Repository<SubscriptionDiscount, EFSubscriptionDiscount, Guid>(
serviceScopeFactory,
mapper,
context => context.SubscriptionDiscounts), ISubscriptionDiscountRepository
{
public async Task<ICollection<SubscriptionDiscount>> GetActiveDiscountsAsync()
{
using var serviceScope = ServiceScopeFactory.CreateScope();
var databaseContext = GetDatabaseContext(serviceScope);
var query =
from subscriptionDiscount in databaseContext.SubscriptionDiscounts
where subscriptionDiscount.StartDate <= DateTime.UtcNow
&& subscriptionDiscount.EndDate >= DateTime.UtcNow
select subscriptionDiscount;
var results = await query.ToArrayAsync();
return Mapper.Map<List<SubscriptionDiscount>>(results);
}
public async Task<SubscriptionDiscount?> GetByStripeCouponIdAsync(string stripeCouponId)
{
using var serviceScope = ServiceScopeFactory.CreateScope();
var databaseContext = GetDatabaseContext(serviceScope);
var query =
from subscriptionDiscount in databaseContext.SubscriptionDiscounts
where subscriptionDiscount.StripeCouponId == stripeCouponId
select subscriptionDiscount;
var result = await query.FirstOrDefaultAsync();
return result == null ? null : Mapper.Map<SubscriptionDiscount>(result);
}
}

View File

@@ -2,6 +2,7 @@
using Bit.Core.Auth.Repositories;
using Bit.Core.Billing.Organizations.Repositories;
using Bit.Core.Billing.Providers.Repositories;
using Bit.Core.Billing.Subscriptions.Repositories;
using Bit.Core.Dirt.Reports.Repositories;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Enums;
@@ -102,6 +103,7 @@ public static class EntityFrameworkServiceCollectionExtensions
services.AddSingleton<IWebAuthnCredentialRepository, WebAuthnCredentialRepository>();
services.AddSingleton<IProviderPlanRepository, ProviderPlanRepository>();
services.AddSingleton<IProviderInvoiceItemRepository, ProviderInvoiceItemRepository>();
services.AddSingleton<ISubscriptionDiscountRepository, SubscriptionDiscountRepository>();
services.AddSingleton<INotificationRepository, NotificationRepository>();
services.AddSingleton<INotificationStatusRepository, NotificationStatusRepository>();
services

View File

@@ -79,6 +79,7 @@ public class DatabaseContext : DbContext
public DbSet<WebAuthnCredential> WebAuthnCredentials { get; set; }
public DbSet<ProviderPlan> ProviderPlans { get; set; }
public DbSet<ProviderInvoiceItem> ProviderInvoiceItems { get; set; }
public DbSet<SubscriptionDiscount> SubscriptionDiscounts { get; set; }
public DbSet<Notification> Notifications { get; set; }
public DbSet<NotificationStatus> NotificationStatuses { get; set; }
public DbSet<ClientOrganizationMigrationRecord> ClientOrganizationMigrationRecords { get; set; }