using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Tiku.Domain.Catalog; using Tiku.Domain.Content; using Tiku.Domain.Identity; using Tiku.Domain.Learning; using Tiku.Domain.QuestionBanks; using Tiku.Domain.Tenancy; namespace Tiku.Infrastructure.Persistence.Configurations; internal sealed class ExamDateConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("exam_dates"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.ExamName).HasMaxLength(300); builder.Property(entity => entity.ExamType).HasMaxLength(50); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.SchoolId, entity.ExamAt }); 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); } } internal sealed class ReportConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("reports"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Type).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.Title).HasMaxLength(300); builder.Property(entity => entity.Category).HasMaxLength(100); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Priority).HasSnakeCaseEnum(); builder.Property(entity => entity.Contact).HasMaxLength(300); builder.Property(entity => entity.Attachments).IsJson("[]"); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.QuestionId, entity.CreatedAt }) .HasFilter("question_id is not null"); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.QuestionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => entity.HandledBy) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class ReportStatusEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("report_status_events"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.FromStatus).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.ToStatus).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.ReportId, entity.CreatedAt }); builder.HasOne().WithMany() .HasForeignKey(entity => entity.TenantId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ReportId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => entity.ActorUserId) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class UserScoreEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("user_score_events"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.EventType).HasSnakeCaseEnum(); builder.Property(entity => entity.SourceType).HasMaxLength(100); builder.Property(entity => entity.IdempotencyKey).HasMaxLength(200); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.IdempotencyKey }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.CreatedAt }); builder.HasOne().WithMany() .HasForeignKey(entity => entity.TenantId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class PracticeDailyUsageConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("practice_daily_usage"); builder.ConfigureTimestamps(); builder.Property(entity => entity.UsageDate).HasDefaultValueSql("current_date"); builder.Property(entity => entity.ScopeType).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.UsageDate }); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.UsageDate, entity.ScopeType, entity.ScopeId }).IsUnique().AreNullsDistinct(false); builder.ToTable(table => { table.HasCheckConstraint("ck_practice_daily_usage_free_limit", "free_limit >= 0"); table.HasCheckConstraint("ck_practice_daily_usage_used_count", "used_count >= 0"); }); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class PracticeAccessEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("practice_access_events"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.EventType).HasSnakeCaseEnum(); builder.Property(entity => entity.AccessMode).HasSnakeCaseEnum(); builder.Property(entity => entity.ScopeType).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.Reason).HasMaxLength(1000); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.PracticeSessionId }); builder.ToTable(table => { table.HasCheckConstraint("ck_practice_access_events_requested_count", "requested_count >= 0"); table.HasCheckConstraint("ck_practice_access_events_granted_count", "granted_count >= 0"); table.HasCheckConstraint("ck_practice_access_events_consumed_free_quota", "consumed_free_quota >= 0"); }); builder.HasOne().WithMany() .HasForeignKey(entity => entity.TenantId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("practice_session_reports"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Mode).HasMaxLength(50); builder.Property(entity => entity.Score).HasPrecision(10, 2); builder.Property(entity => entity.TotalScore).HasPrecision(10, 2); builder.Property(entity => entity.Accuracy).HasPrecision(6, 4); builder.Property(entity => entity.SubmittedAt).HasDefaultValueSql("now()"); builder.Property(entity => entity.Status).HasSnakeCaseEnum().HasDefaultValue(PracticeReportStatus.Final); builder.Property(entity => entity.Version).HasDefaultValue(1); builder.Property(entity => entity.IsFinal).HasDefaultValue(true); builder.Property(entity => entity.ScoringVersion).HasDefaultValue(1); builder.Property(entity => entity.SectionStats).IsJson("[]"); builder.Property(entity => entity.QuestionResults).IsJson("[]"); builder.Property(entity => entity.WrongQuestionIds).IsJson("[]"); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.PracticeSessionId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.SubmittedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.BlueprintId, entity.SubmittedAt }) .HasFilter("blueprint_id is not null"); builder.ToTable(table => { table.HasCheckConstraint("ck_practice_session_reports_counts", "total_questions >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); table.HasCheckConstraint("ck_practice_session_reports_scores", "score >= 0 and total_score >= 0"); table.HasCheckConstraint("ck_practice_session_reports_accuracy", "accuracy >= 0 and accuracy <= 1"); table.HasCheckConstraint("ck_practice_session_reports_duration", "duration_seconds >= 0"); }); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.BlueprintId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.CollectionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class PracticeSessionReportSectionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("practice_session_report_sections"); builder.ConfigureTimestamps(); builder.Property(entity => entity.SectionKey).HasMaxLength(100); builder.Property(entity => entity.SectionName).HasMaxLength(300); builder.Property(entity => entity.QuestionType).HasMaxLength(50); builder.Property(entity => entity.Score).HasPrecision(10, 2); builder.Property(entity => entity.TotalScore).HasPrecision(10, 2); builder.Property(entity => entity.Accuracy).HasPrecision(6, 4); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.ReportId, entity.SectionKey }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.ReportId, entity.SortOrder }); builder.ToTable(table => { table.HasCheckConstraint("ck_practice_session_report_sections_counts", "question_count >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); table.HasCheckConstraint("ck_practice_session_report_sections_scores", "score >= 0 and total_score >= 0"); table.HasCheckConstraint("ck_practice_session_report_sections_accuracy", "accuracy >= 0 and accuracy <= 1"); }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ReportId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class DashboardDailyStatConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("dashboard_daily_stats"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.LegacyRegionId).HasMaxLength(64); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.StatDate, entity.RegionId }).IsUnique() .AreNullsDistinct(false); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class RevenueDailyStatConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("revenue_daily_stats"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.LegacyRegionId).HasMaxLength(64); builder.Property(entity => entity.SaleType).HasMaxLength(50); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.StatDate, entity.RegionId, entity.SaleType }) .IsUnique().AreNullsDistinct(false); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } }