using Cleanuparr.Persistence.Converters; using Cleanuparr.Persistence.Models.Auth; 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; } public UsersContext() { } public UsersContext(DbContextOptions options) : base(options) { } public static UsersContext CreateStaticInstance() { var optionsBuilder = new DbContextOptionsBuilder(); SetDbContextOptions(optionsBuilder); return new UsersContext(optionsBuilder.Options); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { SetDbContextOptions(optionsBuilder); } protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { configurationBuilder.Properties() .HaveConversion(); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { entity.HasIndex(u => u.Username).IsUnique(); entity.HasIndex(u => u.ApiKey).IsUnique(); entity.ComplexProperty(u => u.Oidc); 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); }); } private static void SetDbContextOptions(DbContextOptionsBuilder optionsBuilder) { if (optionsBuilder.IsConfigured) { return; } var dbPath = Path.Combine(ConfigurationPathProvider.GetConfigPath(), "users.db"); optionsBuilder .UseSqlite($"Data Source={dbPath}") .UseLowerCaseNamingConvention() .UseSnakeCaseNamingConvention(); } }