Files

120 lines
3.5 KiB
C#

using Cleanuparr.Persistence.Converters;
using Cleanuparr.Persistence.Models.Auth;
using Cleanuparr.Persistence.Providers;
using Cleanuparr.Shared.Helpers;
using Microsoft.EntityFrameworkCore;
namespace Cleanuparr.Persistence;
/// <summary>
/// Database context for user authentication data
/// </summary>
public class UsersContext : DbContext
{
public static SemaphoreSlim Lock { get; } = new(1, 1);
public DbSet<User> Users { get; set; }
public DbSet<RecoveryCode> RecoveryCodes { get; set; }
public DbSet<RefreshToken> RefreshTokens { get; set; }
/// <summary>
/// Per-user feature-view records (first-seen timestamps) backing the "NEW" feature badges.
/// </summary>
public DbSet<UserFeatureView> UserFeatureViews { get; set; }
private readonly IDatabaseProvider _provider;
public UsersContext() : this(DatabaseProviderFactory.Current)
{
}
public UsersContext(IDatabaseProvider provider)
{
_provider = provider;
}
public UsersContext(DbContextOptions<UsersContext> options) : this(options, DatabaseProviderFactory.Current)
{
}
public UsersContext(DbContextOptions<UsersContext> options, IDatabaseProvider provider) : base(options)
{
_provider = provider;
}
public static UsersContext CreateStaticInstance()
{
IDatabaseProvider provider = DatabaseProviderFactory.Current;
DbContextOptionsBuilder<UsersContext> 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<User>(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<RefreshToken>(entity =>
{
entity.HasIndex(r => r.TokenHash).IsUnique();
});
modelBuilder.Entity<UserFeatureView>(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);
}
}