diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/IQueryContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/IQueryContext.cs new file mode 100644 index 000000000..b5ac7501a --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/IQueryContext.cs @@ -0,0 +1,14 @@ +using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context; + +public interface IQueryContext +{ + IQueryable FilterLists { get; } + IQueryable Languages { get; } + IQueryable Licenses { get; } + IQueryable Maintainers { get; } + IQueryable Software { get; } + IQueryable Syntaxes { get; } + IQueryable Tags { get; } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryContext.cs new file mode 100644 index 000000000..815583569 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryContext.cs @@ -0,0 +1,25 @@ +using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; +using Microsoft.EntityFrameworkCore; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context; + +internal sealed class QueryContext(QueryDbContext dbContext) : IQueryContext, IAsyncDisposable, IDisposable +{ + public ValueTask DisposeAsync() + { + return dbContext.DisposeAsync(); + } + + void IDisposable.Dispose() + { + dbContext.Dispose(); + } + + public IQueryable FilterLists => dbContext.FilterLists.AsNoTracking(); + public IQueryable Languages => dbContext.Languages.AsNoTracking(); + public IQueryable Licenses => dbContext.Licenses.AsNoTracking(); + public IQueryable Maintainers => dbContext.Maintainers.AsNoTracking(); + public IQueryable Software => dbContext.Software.AsNoTracking(); + public IQueryable Syntaxes => dbContext.Syntaxes.AsNoTracking(); + public IQueryable Tags => dbContext.Tags.AsNoTracking(); +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs new file mode 100644 index 000000000..d4e210368 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs @@ -0,0 +1,34 @@ +using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; +using Microsoft.EntityFrameworkCore; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context; + +public class QueryDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet FilterLists => Set(); + public DbSet Languages => Set(); + public DbSet Licenses => Set(); + public DbSet Maintainers => Set(); + public DbSet Software => Set(); + public DbSet Syntaxes => Set(); + public DbSet Tags => Set(); + + public override int SaveChanges(bool acceptAllChangesOnSuccess) + { + throw new InvalidOperationException("This context is read-only."); + } + + public override Task SaveChangesAsync( + bool acceptAllChangesOnSuccess, + CancellationToken cancellationToken = default) + { + throw new InvalidOperationException("This context is read-only."); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfigurationsFromAssembly( + GetType().Assembly, + type => type.Namespace == typeof(FilterListTypeConfiguration).Namespace); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Dependent.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Dependent.cs new file mode 100644 index 000000000..3be02abf9 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Dependent.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Dependent +{ + public long DependencyFilterListId { get; init; } + public FilterList DependencyFilterList { get; init; } = default!; + public long DependentFilterListId { get; init; } + public FilterList DependentFilterList { get; init; } = default!; +} + +internal class DependentTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(d => new { d.DependencyFilterListId, d.DependentFilterListId }); + builder.HasOne(d => d.DependencyFilterList) + .WithMany(fl => fl.DependentFilterLists) + .HasForeignKey(d => d.DependencyFilterListId); + builder.HasOne(d => d.DependentFilterList) + .WithMany(fl => fl.DependencyFilterLists) + .HasForeignKey(d => d.DependentFilterListId); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Entity.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Entity.cs new file mode 100644 index 000000000..7c8fa089b --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Entity.cs @@ -0,0 +1,6 @@ +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public abstract record Entity +{ + public long Id { get; init; } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterList.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterList.cs new file mode 100644 index 000000000..0702586b2 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterList.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record FilterList : Entity +{ + public string Name { get; init; } = default!; + public string? Description { get; init; } + public long LicenseId { get; init; } + public License License { get; init; } = default!; + public IEnumerable FilterListSyntaxes { get; init; } = new HashSet(); + public IEnumerable FilterListLanguages { get; init; } = new HashSet(); + public IEnumerable FilterListTags { get; init; } = new HashSet(); + public IEnumerable ViewUrls { get; init; } = new HashSet(); + public Uri? HomeUrl { get; init; } + public Uri? OnionUrl { get; init; } + public Uri? PolicyUrl { get; init; } + public Uri? SubmissionUrl { get; init; } + public Uri? IssuesUrl { get; init; } + public Uri? ForumUrl { get; init; } + public Uri? ChatUrl { get; init; } + public string? EmailAddress { get; init; } + public Uri? DonateUrl { get; init; } + public IEnumerable FilterListMaintainers { get; init; } = new HashSet(); + public IEnumerable UpstreamFilterLists { get; init; } = new HashSet(); + public IEnumerable ForkFilterLists { get; init; } = new HashSet(); + public IEnumerable IncludedInFilterLists { get; init; } = new HashSet(); + public IEnumerable IncludesFilterLists { get; init; } = new HashSet(); + public IEnumerable DependencyFilterLists { get; init; } = new HashSet(); + public IEnumerable DependentFilterLists { get; init; } = new HashSet(); +} + +internal class FilterListTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasIndex(f => f.Name) + .IsUnique(); + builder.Property(f => f.LicenseId) + .HasDefaultValue(5); + builder.HasOne(f => f.License) + .WithMany(l => l.FilterLists) + .OnDelete(DeleteBehavior.Restrict); + builder.OwnsMany( + f => f.ViewUrls, + b => + { + b.Property(u => u.SegmentNumber) + .HasDefaultValue(1); + b.Property(u => u.Primariness) + .HasDefaultValue(1); + b.HasIndex(u => new { u.FilterListId, u.SegmentNumber, u.Primariness }) + .IsUnique(); + b.HasDataJsonFile(); + }); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListLanguage.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListLanguage.cs new file mode 100644 index 000000000..46275e725 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListLanguage.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record FilterListLanguage +{ + public long FilterListId { get; init; } + public FilterList FilterList { get; init; } = default!; + public long LanguageId { get; init; } + public Language Language { get; init; } = default!; +} + +internal class FilterListLanguageTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(fll => new { fll.FilterListId, fll.LanguageId }); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListMaintainer.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListMaintainer.cs new file mode 100644 index 000000000..8769d8e8d --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListMaintainer.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record FilterListMaintainer +{ + public long FilterListId { get; init; } + public FilterList FilterList { get; init; } = default!; + public long MaintainerId { get; init; } + public Maintainer Maintainer { get; init; } = default!; +} + +internal class FilterListMaintainerTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(flm => new { flm.FilterListId, flm.MaintainerId }); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListSyntax.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListSyntax.cs new file mode 100644 index 000000000..f984ff38f --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListSyntax.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record FilterListSyntax +{ + public long FilterListId { get; init; } + public FilterList FilterList { get; init; } = default!; + public long SyntaxId { get; init; } + public Syntax Syntax { get; init; } = default!; +} + +internal class FilterListSyntaxTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(fls => new { fls.FilterListId, fls.SyntaxId }); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListTag.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListTag.cs new file mode 100644 index 000000000..4577b7502 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListTag.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record FilterListTag +{ + public long FilterListId { get; init; } + public FilterList FilterList { get; init; } = default!; + public long TagId { get; init; } + public Tag Tag { get; init; } = default!; +} + +internal class FilterListTagTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(flt => new { flt.FilterListId, flt.TagId }); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListViewUrl.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListViewUrl.cs new file mode 100644 index 000000000..cf389aa89 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListViewUrl.cs @@ -0,0 +1,10 @@ +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record FilterListViewUrl : Entity +{ + public long FilterListId { get; init; } + public FilterList FilterList { get; init; } = default!; + public short SegmentNumber { get; init; } + public short Primariness { get; init; } + public Uri Url { get; init; } = default!; +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Fork.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Fork.cs new file mode 100644 index 000000000..16fc98ad4 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Fork.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Fork +{ + public long UpstreamFilterListId { get; init; } + public FilterList UpstreamFilterList { get; init; } = default!; + public long ForkFilterListId { get; init; } + public FilterList ForkFilterList { get; init; } = default!; +} + +internal class ForkTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(f => new { f.UpstreamFilterListId, f.ForkFilterListId }); + builder.HasOne(f => f.UpstreamFilterList) + .WithMany(fl => fl.ForkFilterLists) + .HasForeignKey(f => f.UpstreamFilterListId); + builder.HasOne(f => f.ForkFilterList) + .WithMany(fl => fl.UpstreamFilterLists) + .HasForeignKey(f => f.ForkFilterListId); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Language.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Language.cs new file mode 100644 index 000000000..9e44680ee --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Language.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Language : Entity +{ + public string Iso6391 { get; init; } = default!; + public string Name { get; init; } = default!; + public IEnumerable FilterListLanguages { get; init; } = new HashSet(); +} + +internal class LanguageTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.Property(l => l.Iso6391) + .IsFixedLength() + .HasMaxLength(2); + builder.HasIndex(l => l.Iso6391) + .IsUnique(); + builder.HasIndex(l => l.Name) + .IsUnique(); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/License.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/License.cs new file mode 100644 index 000000000..3abdbf8ec --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/License.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record License : Entity +{ + public string Name { get; init; } = default!; + public Uri? Url { get; init; } + public bool PermitsModification { get; init; } + public bool PermitsDistribution { get; init; } + public bool PermitsCommercialUse { get; init; } + public IEnumerable FilterLists { get; init; } = new HashSet(); +} + +internal class LicenseTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasIndex(l => l.Name) + .IsUnique(); + builder.Property(l => l.PermitsModification) + .HasDefaultValue(false); + builder.Property(l => l.PermitsDistribution) + .HasDefaultValue(false); + builder.Property(l => l.PermitsCommercialUse) + .HasDefaultValue(false); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Maintainer.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Maintainer.cs new file mode 100644 index 000000000..17c64d82b --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Maintainer.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Maintainer : Entity +{ + public string Name { get; init; } = default!; + public Uri? Url { get; init; } + public string? EmailAddress { get; init; } + public string? TwitterHandle { get; init; } + public IEnumerable FilterListMaintainers { get; init; } = new HashSet(); +} + +internal class MaintainerTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasIndex(m => m.Name) + .IsUnique(); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Merge.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Merge.cs new file mode 100644 index 000000000..3ade4b6ee --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Merge.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Merge +{ + public long IncludedInFilterListId { get; init; } + public FilterList IncludedInFilterList { get; init; } = default!; + public long IncludesFilterListId { get; init; } + public FilterList IncludesFilterList { get; init; } = default!; +} + +internal class MergeTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(m => new { m.IncludedInFilterListId, m.IncludesFilterListId }); + builder.HasOne(m => m.IncludedInFilterList) + .WithMany(fl => fl.IncludesFilterLists) + .HasForeignKey(m => m.IncludedInFilterListId); + builder.HasOne(m => m.IncludesFilterList) + .WithMany(fl => fl.IncludedInFilterLists) + .HasForeignKey(m => m.IncludesFilterListId); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Software.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Software.cs new file mode 100644 index 000000000..b39478c99 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Software.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Software : Entity +{ + public string Name { get; init; } = default!; + public string? Description { get; init; } + public Uri? HomeUrl { get; init; } + public Uri? DownloadUrl { get; init; } + public bool SupportsAbpUrlScheme { get; init; } + public IEnumerable SoftwareSyntaxes { get; init; } = new HashSet(); +} + +internal class SoftwareTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasIndex(s => s.Name) + .IsUnique(); + builder.Property(s => s.SupportsAbpUrlScheme) + .HasDefaultValue(false); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/SoftwareSyntax.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/SoftwareSyntax.cs new file mode 100644 index 000000000..1af337dcc --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/SoftwareSyntax.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record SoftwareSyntax +{ + public long SoftwareId { get; init; } + public Software Software { get; init; } = default!; + public long SyntaxId { get; init; } + public Syntax Syntax { get; init; } = default!; +} + +internal class SoftwareSyntaxTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ss => new { ss.SoftwareId, ss.SyntaxId }); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Syntax.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Syntax.cs new file mode 100644 index 000000000..dd70fac6c --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Syntax.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Syntax : Entity +{ + public string Name { get; init; } = default!; + public string? Description { get; init; } + public Uri? Url { get; init; } + public IEnumerable FilterListSyntaxes { get; init; } = new HashSet(); + public IEnumerable SoftwareSyntaxes { get; init; } = new HashSet(); +} + +internal class SyntaxTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasIndex(s => s.Name) + .IsUnique(); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Tag.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Tag.cs new file mode 100644 index 000000000..04349024a --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Tag.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; + +public record Tag : Entity +{ + public string Name { get; init; } = default!; + public string? Description { get; init; } + public IEnumerable FilterListTags { get; init; } = new HashSet(); +} + +internal class TagTypeConfiguration : IEntityTypeConfiguration +{ + public virtual void Configure(EntityTypeBuilder builder) + { + builder.HasIndex(t => t.Name) + .IsUnique(); + builder.HasDataJsonFile(); + } +} \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/SeedExtensions.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/SeedExtensions.cs new file mode 100644 index 000000000..a996d514e --- /dev/null +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/SeedExtensions.cs @@ -0,0 +1,56 @@ +using System.Text.Json; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace FilterLists.Directory.Infrastructure.Persistence; + +public static class SeedExtension +{ + public static async Task MigrateAsync(this IHost host) + { + using var scope = host.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); + } +} + +internal static class SeedConfigurationExtensions +{ + private static readonly JsonSerializerOptions CamelCaseJsonSerializerOptions = + new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + public static void HasDataJsonFile(this EntityTypeBuilder entityTypeBuilder) where TEntity : class + { + var entities = Deserialize(); + if (entities.Count == 0) return; + + entityTypeBuilder.HasData(entities); + } + + public static void HasDataJsonFile(this OwnedNavigationBuilder ownedNavigationBuilder) + where TEntity : class + { + var entities = Deserialize(); + if (entities.Count == 0) return; + + ownedNavigationBuilder.HasData(entities); + } + + private static List Deserialize() + { + // uncomment to short-circuit HasData() when adding a migration + //return new List(); + + var path = Path.Combine("../data", $"{typeof(TEntity).Name}.json"); + if (!File.Exists(path)) return []; + + var entitiesJson = File.ReadAllText(path); + var entities = JsonSerializer.Deserialize>( + entitiesJson, + CamelCaseJsonSerializerOptions); + return entities is null ? [] : entities.ToList(); + } +} \ No newline at end of file