Files

323 lines
14 KiB
C#

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<VocabularyUnit>
{
public void Configure(EntityTypeBuilder<VocabularyUnit> 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<Region>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentEntry>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.EntryId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentNode>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class VocabularyWordConfiguration : IEntityTypeConfiguration<VocabularyWord>
{
public void Configure(EntityTypeBuilder<VocabularyWord> 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<VocabularyUnit>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.UnitId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentEntry>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.EntryId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentNode>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class UserWordProgressConfiguration : IEntityTypeConfiguration<UserWordProgress>
{
public void Configure(EntityTypeBuilder<UserWordProgress> 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<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<VocabularyWord>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.WordId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class UserWordFavoriteConfiguration : IEntityTypeConfiguration<UserWordFavorite>
{
public void Configure(EntityTypeBuilder<UserWordFavorite> 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<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<VocabularyWord>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.WordId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class HandbookSubjectConfiguration : IEntityTypeConfiguration<HandbookSubject>
{
public void Configure(EntityTypeBuilder<HandbookSubject> 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<Region>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<School>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.SchoolId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Major>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.MajorId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentEntry>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.EntryId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentNode>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class HandbookChapterConfiguration : IEntityTypeConfiguration<HandbookChapter>
{
public void Configure(EntityTypeBuilder<HandbookChapter> 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<HandbookSubject>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.SubjectId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<ContentEntry>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.EntryId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentNode>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class HandbookEntryConfiguration : IEntityTypeConfiguration<HandbookEntry>
{
public void Configure(EntityTypeBuilder<HandbookEntry> 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<HandbookChapter>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ChapterId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<ContentEntry>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.EntryId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentNode>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class QuestionTypeGroupConfiguration : IEntityTypeConfiguration<QuestionTypeGroup>
{
public void Configure(EntityTypeBuilder<QuestionTypeGroup> 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<Subject>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.SubjectId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class SubjectShareConfiguration : IEntityTypeConfiguration<SubjectShare>
{
public void Configure(EntityTypeBuilder<SubjectShare> 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<Tenant>().WithMany()
.HasForeignKey(entity => entity.TenantId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<Subject>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.SourceSubjectId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<Subject>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.TargetSubjectId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}