diff --git a/JSMR.Domain/Entities/Circle.cs b/JSMR.Domain/Entities/Circle.cs index fd38b6a..648ef18 100644 --- a/JSMR.Domain/Entities/Circle.cs +++ b/JSMR.Domain/Entities/Circle.cs @@ -8,4 +8,6 @@ public class Circle public bool Blacklisted { get; set; } public bool Favorite { get; set; } public bool Spam { get; set; } + + public virtual ICollection VoiceWorks { get; set; } = []; } \ No newline at end of file diff --git a/JSMR.Domain/Entities/Series.cs b/JSMR.Domain/Entities/Series.cs index da82bbd..aaea82b 100644 --- a/JSMR.Domain/Entities/Series.cs +++ b/JSMR.Domain/Entities/Series.cs @@ -9,4 +9,6 @@ public class Series public required string Name { get; set; } public required string Identifier { get; set; } + + public virtual ICollection VoiceWorks { get; set; } = []; } \ No newline at end of file diff --git a/JSMR.Domain/Entities/VoiceWork.cs b/JSMR.Domain/Entities/VoiceWork.cs index bedec35..8839280 100644 --- a/JSMR.Domain/Entities/VoiceWork.cs +++ b/JSMR.Domain/Entities/VoiceWork.cs @@ -39,4 +39,5 @@ public class VoiceWork public virtual ICollection VoiceWorkTags { get; set; } = []; public virtual ICollection VoiceWorkCreators { get; set; } = []; public virtual ICollection EnglishVoiceWorks { get; set; } = []; + public virtual ICollection Localizations { get; set; } = []; } \ No newline at end of file diff --git a/JSMR.Domain/Entities/VoiceWorkLocalization.cs b/JSMR.Domain/Entities/VoiceWorkLocalization.cs new file mode 100644 index 0000000..37ce478 --- /dev/null +++ b/JSMR.Domain/Entities/VoiceWorkLocalization.cs @@ -0,0 +1,13 @@ +namespace JSMR.Domain.Entities; + +public sealed class VoiceWorkLocalization +{ + public int VoiceWorkLocalizationId { get; set; } + public int VoiceWorkId { get; set; } + public string Language { get; set; } = null!; // "ja-JP", "en", etc. + public string? ProductName { get; set; } + public string? Description { get; set; } + public bool IsOfficial { get; set; } + + public VoiceWork VoiceWork { get; set; } = null!; +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/AppDbContext.cs b/JSMR.Infrastructure/Data/AppDbContext.cs index 717946e..c49b8a5 100644 --- a/JSMR.Infrastructure/Data/AppDbContext.cs +++ b/JSMR.Infrastructure/Data/AppDbContext.cs @@ -7,6 +7,7 @@ public class AppDbContext : DbContext { public DbSet VoiceWorks { get; set; } public DbSet EnglishVoiceWorks { get; set; } + public DbSet VoiceWorkLocalizations { get; set; } public DbSet Circles { get; set; } public DbSet Tags { get; set; } public DbSet EnglishTags { get; set; } @@ -15,4 +16,9 @@ public class AppDbContext : DbContext public DbSet VoiceWorkCreators { get; set; } public DbSet Series { get; set; } public DbSet VoiceWorkSearches { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly); + } } \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/CircleConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/CircleConfiguration.cs new file mode 100644 index 0000000..863dd31 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/CircleConfiguration.cs @@ -0,0 +1,23 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class CircleConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("circles"); + builder.HasKey(x => x.CircleId); + + builder.Property(x => x.Name).IsRequired().HasMaxLength(256); + builder.Property(x => x.MakerId).IsRequired().HasMaxLength(16); + + builder.HasIndex(x => x.Name); + builder.HasIndex(x => x.MakerId).IsUnique(); + builder.HasIndex(x => x.Favorite); + builder.HasIndex(x => x.Blacklisted); + builder.HasIndex(x => x.Spam); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/CreatorConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/CreatorConfiguration.cs new file mode 100644 index 0000000..436b4e8 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/CreatorConfiguration.cs @@ -0,0 +1,20 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class CreatorConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("creators"); + builder.HasKey(x => x.CreatorId); + + builder.Property(x => x.Name).IsRequired().HasMaxLength(128); + + builder.HasIndex(x => x.Name); + builder.HasIndex(x => x.Favorite); + builder.HasIndex(x => x.Blacklisted); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/EnglishTagConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/EnglishTagConfiguration.cs new file mode 100644 index 0000000..ecc6adb --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/EnglishTagConfiguration.cs @@ -0,0 +1,24 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class EnglishTagConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("english_tags"); + builder.HasKey(x => x.EnglishTagId); + + builder.Property(x => x.Name).IsRequired().HasMaxLength(128); + + builder.HasOne(x => x.Tag) + .WithMany() + .HasForeignKey(x => x.TagId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasIndex(x => x.TagId).IsUnique(); + builder.HasIndex(x => x.Name); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/EnglishVoiceWorkConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/EnglishVoiceWorkConfiguration.cs new file mode 100644 index 0000000..f2a9302 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/EnglishVoiceWorkConfiguration.cs @@ -0,0 +1,25 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class EnglishVoiceWorkConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("english_voice_works"); + builder.HasKey(x => x.EnglishVoiceWorkId); + + builder.Property(x => x.ProductName).HasMaxLength(256); + builder.Property(x => x.Description).HasMaxLength(512); + + builder.HasOne(x => x.VoiceWork) + .WithMany(v => v.EnglishVoiceWorks) + .HasForeignKey(x => x.VoiceWorkId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasIndex(x => x.VoiceWorkId).IsUnique(); + builder.HasIndex(x => x.IsValid); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/TagConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/TagConfiguration.cs new file mode 100644 index 0000000..c0f297f --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/TagConfiguration.cs @@ -0,0 +1,20 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class TagConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("tags"); + builder.HasKey(x => x.TagId); + + builder.Property(x => x.Name).IsRequired().HasMaxLength(128); + + builder.HasIndex(x => x.Name).IsUnique(); + builder.HasIndex(x => x.Favorite); + builder.HasIndex(x => x.Blacklisted); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/VoiceWorkConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/VoiceWorkConfiguration.cs new file mode 100644 index 0000000..3a550f3 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/VoiceWorkConfiguration.cs @@ -0,0 +1,51 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class VoiceWorkConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("voice_works"); + builder.HasKey(x => x.VoiceWorkId); + + builder.Property(x => x.ProductId) + .IsRequired() + .HasMaxLength(16); + + builder.Property(x => x.OriginalProductId) + .HasMaxLength(16); + + builder.Property(x => x.ProductName) + .IsRequired() + .HasMaxLength(256); + + builder.Property(x => x.Description) + .HasMaxLength(512); + + // Relationships + builder.HasOne(x => x.Circle) + .WithMany(c => c.VoiceWorks) + .HasForeignKey(x => x.CircleId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(x => x.Series) + .WithMany(s => s.VoiceWorks) + .HasForeignKey(x => x.SeriesId) + .OnDelete(DeleteBehavior.SetNull); + + // Indexes for common filters/sorts + builder.HasIndex(x => x.ProductId).IsUnique(); + builder.HasIndex(x => x.CircleId); + builder.HasIndex(x => x.SalesDate); + builder.HasIndex(x => x.ExpectedDate); + builder.HasIndex(x => x.Favorite); + builder.HasIndex(x => x.IsValid); + builder.HasIndex(x => x.SubtitleLanguage); + builder.HasIndex(x => x.AIGeneration); + builder.HasIndex(x => x.Downloads); + builder.HasIndex(x => x.WishlistCount); + } +} diff --git a/JSMR.Infrastructure/Data/Configuration/VoiceWorkCreatorConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/VoiceWorkCreatorConfiguration.cs new file mode 100644 index 0000000..fb66009 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/VoiceWorkCreatorConfiguration.cs @@ -0,0 +1,28 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class VoiceWorkCreatorConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("voice_work_creators"); + builder.HasKey(x => new { x.VoiceWorkId, x.CreatorId }); + + builder.HasOne(x => x.VoiceWork) + .WithMany(v => v.VoiceWorkCreators) + .HasForeignKey(x => x.VoiceWorkId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(x => x.Creator) + .WithMany() + .HasForeignKey(x => x.CreatorId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasIndex(x => x.CreatorId); + builder.HasIndex(x => new { x.VoiceWorkId, x.Position }); + builder.HasIndex(x => x.IsValid); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/VoiceWorkLocalizationConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/VoiceWorkLocalizationConfiguration.cs new file mode 100644 index 0000000..ddfbbe1 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/VoiceWorkLocalizationConfiguration.cs @@ -0,0 +1,27 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class VoiceWorkLocalizationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("voice_work_localizations"); + builder.HasKey(x => x.VoiceWorkLocalizationId); + + builder.Property(x => x.Language).IsRequired().HasMaxLength(10); + builder.Property(x => x.ProductName).HasMaxLength(256); + builder.Property(x => x.Description).HasMaxLength(512); + + builder.HasOne(x => x.VoiceWork) + .WithMany(v => v.Localizations) + .HasForeignKey(x => x.VoiceWorkId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasIndex(x => new { x.VoiceWorkId, x.Language }).IsUnique(); + builder.HasIndex(x => x.VoiceWorkId); + builder.HasIndex(x => x.ProductName); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/VoiceWorkSearchConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/VoiceWorkSearchConfiguration.cs new file mode 100644 index 0000000..4299716 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/VoiceWorkSearchConfiguration.cs @@ -0,0 +1,27 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class VoiceWorkSearchConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("voice_work_searches"); + builder.HasKey(x => x.VoiceWorkId); // also the FK + + builder.Property(x => x.SearchText).IsRequired(); // TEXT/LONGTEXT + + builder.HasOne(x => x.VoiceWork) + .WithOne() // not navigated from VoiceWork in your model + .HasForeignKey(x => x.VoiceWorkId) + .OnDelete(DeleteBehavior.Cascade); + + // MariaDB/MySQL (Pomelo) fulltext (BOOLEAN/NATURAL) — create an FT index + // Pomelo supports .HasMethod("FULLTEXT"). If your version doesn't, add it in a migration SQL. + //builder.HasIndex(x => x.SearchText) + // .HasDatabaseName("FT_SearchText") + // .HasMethod("FULLTEXT"); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Configuration/VoiceWorkTagConfiguration.cs b/JSMR.Infrastructure/Data/Configuration/VoiceWorkTagConfiguration.cs new file mode 100644 index 0000000..947acf8 --- /dev/null +++ b/JSMR.Infrastructure/Data/Configuration/VoiceWorkTagConfiguration.cs @@ -0,0 +1,29 @@ +using JSMR.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace JSMR.Infrastructure.Data.Configuration; + +public sealed class VoiceWorkTagConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("voice_work_tags"); + builder.HasKey(x => new { x.VoiceWorkId, x.TagId }); + + builder.HasOne(x => x.VoiceWork) + .WithMany(v => v.VoiceWorkTags) + .HasForeignKey(x => x.VoiceWorkId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(x => x.Tag) + .WithMany() + .HasForeignKey(x => x.TagId) + .OnDelete(DeleteBehavior.Cascade); + + // Helpful indexes + builder.HasIndex(x => x.TagId); + builder.HasIndex(x => new { x.VoiceWorkId, x.Position }); + builder.HasIndex(x => x.IsValid); + } +} \ No newline at end of file diff --git a/JSMR.Infrastructure/JSMR.Infrastructure.csproj b/JSMR.Infrastructure/JSMR.Infrastructure.csproj index dccbd04..634d068 100644 --- a/JSMR.Infrastructure/JSMR.Infrastructure.csproj +++ b/JSMR.Infrastructure/JSMR.Infrastructure.csproj @@ -8,6 +8,7 @@ +