using Cleanuparr.Persistence.Converters; using Cleanuparr.Persistence.Models.Auth; using Cleanuparr.Persistence.Providers; using Cleanuparr.Shared.Helpers; using Microsoft.EntityFrameworkCore; namespace Cleanuparr.Persistence; /// /// Database context for user authentication data /// public class UsersContext : DbContext { public static SemaphoreSlim Lock { get; } = new(1, 1); public DbSet Users { get; set; } public DbSet RecoveryCodes { get; set; } public DbSet RefreshTokens { get; set; } /// /// Per-user feature-view records (first-seen timestamps) backing the "NEW" feature badges. /// public DbSet UserFeatureViews { get; set; } private readonly IDatabaseProvider _provider; public UsersContext() : this(DatabaseProviderFactory.Current) { } public UsersContext(IDatabaseProvider provider) { _provider = provider; } public UsersContext(DbContextOptions options) : this(options, DatabaseProviderFactory.Current) { } public UsersContext(DbContextOptions options, IDatabaseProvider provider) : base(options) { _provider = provider; } public static UsersContext CreateStaticInstance() { IDatabaseProvider provider = DatabaseProviderFactory.Current; DbContextOptionsBuilder optionsBuilder = new(); provider.ConfigureContext(optionsBuilder, DbContextKind.Users); return new UsersContext(optionsBuilder.Options, provider); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { SetDbContextOptions(optionsBuilder); } protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { _provider.ConfigureConventions(configurationBuilder); } protected override void OnModelCreating(ModelBuilder modelBuilder) { string? schema = _provider.GetSchema(DbContextKind.Users); if (schema is not null) { modelBuilder.HasDefaultSchema(schema); } modelBuilder.Entity(entity => { entity.HasIndex(u => u.Username).IsUnique(); entity.HasIndex(u => u.ApiKey).IsUnique(); entity.HasMany(u => u.RecoveryCodes) .WithOne(r => r.User) .HasForeignKey(r => r.UserId) .OnDelete(DeleteBehavior.Cascade); entity.HasMany(u => u.RefreshTokens) .WithOne(r => r.User) .HasForeignKey(r => r.UserId) .OnDelete(DeleteBehavior.Cascade); entity.HasMany(u => u.FeatureViews) .WithOne(v => v.User) .HasForeignKey(v => v.UserId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity.HasIndex(r => r.TokenHash).IsUnique(); }); modelBuilder.Entity(entity => { entity.HasIndex(v => new { v.UserId, v.FeatureId }).IsUnique(); entity.Property(v => v.FeatureId) .HasMaxLength(64); }); modelBuilder.ApplyLowercaseEnumConversions(); } private void SetDbContextOptions(DbContextOptionsBuilder optionsBuilder) { if (optionsBuilder.IsConfigured) { return; } _provider.ConfigureContext(optionsBuilder, DbContextKind.Users); } }