using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Tiku.Domain.Catalog; using Tiku.Domain.Content; using Tiku.Domain.Identity; using Tiku.Domain.Tenancy; namespace Tiku.Infrastructure.Persistence.Configurations; internal sealed class VocabularyUnitConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("vocabulary_units"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Name).HasMaxLength(300); builder.Property(entity => entity.SourceHash).HasMaxLength(128); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.EntryId, entity.ContentNodeId, entity.RegionId, entity.IsActive, entity.SortOrder }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.EntryId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class VocabularyWordConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("vocabulary_words"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Word).HasMaxLength(300); builder.Property(entity => entity.Phonetic).HasMaxLength(300); builder.Property(entity => entity.SourceHash).HasMaxLength(128); builder.Property(entity => entity.Tags).IsJson("[]"); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.EntryId, entity.ContentNodeId, entity.UnitId, entity.IsActive, entity.SortOrder }); builder.HasIndex(entity => new { entity.TenantId, entity.UnitId, entity.IsActive, entity.SortOrder, entity.CreatedAt }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.UnitId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.EntryId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class UserWordProgressConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("user_word_progress"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.EaseFactor).HasPrecision(4, 2).HasDefaultValue(2.50m); builder.Property(entity => entity.LastResult).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.DueLevel).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.WordId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.NextReviewAt, entity.Status, entity.DueLevel }); builder.ToTable(table => { table.HasCheckConstraint("ck_user_word_progress_review_count", "review_count >= 0"); table.HasCheckConstraint("ck_user_word_progress_correct_streak", "correct_streak >= 0"); table.HasCheckConstraint("ck_user_word_progress_ease_factor", "ease_factor >= 1.30 and ease_factor <= 3.00"); }); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.WordId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class UserWordFavoriteConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("user_word_favorites"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Note).HasMaxLength(1000); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.WordId }).IsUnique(); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.WordId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class HandbookSubjectConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("handbook_subjects"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Name).HasMaxLength(300); builder.Property(entity => entity.Type).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.Icon).HasMaxLength(2048); builder.Property(entity => entity.Color).HasMaxLength(50); builder.Property(entity => entity.MajorLegacyIds).IsJson("[]"); builder.Property(entity => entity.SourceHash).HasMaxLength(128); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.EntryId, entity.ContentNodeId, entity.RegionId, entity.IsActive, entity.SortOrder }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.SchoolId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.MajorId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.EntryId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class HandbookChapterConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("handbook_chapters"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Name).HasMaxLength(300); builder.Property(entity => entity.SourceHash).HasMaxLength(128); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.EntryId, entity.ContentNodeId, entity.SubjectId, entity.IsActive, entity.SortOrder }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.SubjectId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.EntryId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class HandbookEntryConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("handbook_entries"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Title).HasMaxLength(500); builder.Property(entity => entity.Tags).IsJson("[]"); builder.Property(entity => entity.SourceHash).HasMaxLength(128); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.EntryId, entity.ContentNodeId, entity.ChapterId, entity.IsActive, entity.SortOrder }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ChapterId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.EntryId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class QuestionTypeGroupConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("question_type_groups"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.DisplayName).HasMaxLength(200); builder.Property(entity => entity.Types).IsJson("[]"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.SubjectId, entity.IsActive, entity.SortOrder }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.SubjectId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class SubjectShareConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("subject_shares"); builder.HasKey(entity => new { entity.TenantId, entity.SourceSubjectId, entity.TargetSubjectId }); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.TargetSubjectId }); builder.HasOne().WithMany() .HasForeignKey(entity => entity.TenantId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.SourceSubjectId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.TargetSubjectId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } }