using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using IdentityServer4.EntityFramework.Entities; using IdentityServer4.EntityFramework.Extensions; using IdentityServer4.EntityFramework.Interfaces; using IdentityServer4.EntityFramework.Options; using Kyoo.Models; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.Options; namespace Kyoo { public class IdentityDatabase : IdentityDbContext, IPersistedGrantDbContext { private readonly IOptions _operationalStoreOptions; public IdentityDatabase(DbContextOptions options, IOptions operationalStoreOptions) : base(options) { _operationalStoreOptions = operationalStoreOptions; } public DbSet Accounts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value); modelBuilder.Entity().ToTable("User"); modelBuilder.Entity>().ToTable("UserRole"); modelBuilder.Entity>().ToTable("UserLogin"); modelBuilder.Entity>().ToTable("UserClaim"); modelBuilder.Entity().ToTable("UserRoles"); modelBuilder.Entity>().ToTable("UserRoleClaim"); modelBuilder.Entity>().ToTable("UserToken"); } public Task SaveChangesAsync() => base.SaveChangesAsync(); public DbSet PersistedGrants { get; set; } public DbSet DeviceFlowCodes { get; set; } } public class DatabaseContext : DbContext { public DatabaseContext(DbContextOptions options) : base(options) { } public DbSet Libraries { get; set; } public DbSet Collections { get; set; } public DbSet Shows { get; set; } public DbSet Seasons { get; set; } public DbSet Episodes { get; set; } public DbSet Tracks { get; set; } public DbSet Genres { get; set; } public DbSet Peoples { get; set; } public DbSet Studios { get; set; } public DbSet Providers { get; set; } public DbSet MetadataIds { get; set; } public DbSet LibraryLinks { get; set; } public DbSet CollectionLinks { get; set; } public DbSet PeopleLinks { get; set; } // This is used because EF doesn't support Many-To-Many relationships so for now we need to override the getter/setters to store this. public DbSet GenreLinks { get; set; } public DbSet ProviderLinks { get; set; } private readonly ValueConverter, string> _stringArrayConverter = new ValueConverter, string>( arr => string.Join("|", arr), str => str.Split("|", StringSplitOptions.None)); private readonly ValueComparer> _stringArrayComparer = new ValueComparer>( (l1, l2) => l1.SequenceEqual(l2), arr => arr.Aggregate(0, (i, s) => s.GetHashCode())); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity().Property(e => e.Paths) .HasConversion(_stringArrayConverter).Metadata .SetValueComparer(_stringArrayComparer); modelBuilder.Entity().Property(e => e.Aliases) .HasConversion(_stringArrayConverter).Metadata .SetValueComparer(_stringArrayComparer); modelBuilder.Entity() .Property(t => t.IsDefault) .ValueGeneratedNever(); modelBuilder.Entity() .Property(t => t.IsForced) .ValueGeneratedNever(); modelBuilder.Entity() .HasKey(x => new {x.ShowID, x.GenreID}); modelBuilder.Entity() .Ignore(x => x.Shows) .Ignore(x => x.Collections) .Ignore(x => x.Providers); modelBuilder.Entity() .Ignore(x => x.Shows) .Ignore(x => x.Libraries); modelBuilder.Entity() .Ignore(x => x.Genres) .Ignore(x => x.Libraries) .Ignore(x => x.Collections); modelBuilder.Entity() .Ignore(x => x.Slug) .Ignore(x => x.Name) .Ignore(x => x.ExternalIDs); modelBuilder.Entity() .Ignore(x => x.Shows); modelBuilder.Entity() .HasOne(x => x.Show) .WithMany(x => x.ExternalIDs) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(x => x.Season) .WithMany(x => x.ExternalIDs) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(x => x.Episode) .WithMany(x => x.ExternalIDs) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(x => x.People) .WithMany(x => x.ExternalIDs) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Name) .IsUnique(); modelBuilder.Entity() .HasIndex(x => new {x.ShowID, x.SeasonNumber}) .IsUnique(); modelBuilder.Entity() .HasIndex(x => new {x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber}) .IsUnique(); } public void DiscardChanges() { foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged && x.State != EntityState.Detached)) { entry.State = EntityState.Detached; } } } } public static class DbSetExtension { public static EntityEntry AddIfNotExist(this DbSet db, T entity, Func predicate) where T : class { bool exists = db.Any(predicate); return exists ? null : db.Add(entity); } }