Files
tiku-backend.net/Tiku.Infrastructure/Persistence/Configurations/LearningReportConfigurations.cs

295 lines
15 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.Learning;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
namespace Tiku.Infrastructure.Persistence.Configurations;
internal sealed class ExamDateConfiguration : IEntityTypeConfiguration<ExamDate>
{
public void Configure(EntityTypeBuilder<ExamDate> 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<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);
}
}
internal sealed class ReportConfiguration : IEntityTypeConfiguration<Report>
{
public void Configure(EntityTypeBuilder<Report> 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<Question>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.HandledBy)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class ReportStatusEventConfiguration : IEntityTypeConfiguration<ReportStatusEvent>
{
public void Configure(EntityTypeBuilder<ReportStatusEvent> 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<Tenant>().WithMany()
.HasForeignKey(entity => entity.TenantId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<Report>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ReportId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.ActorUserId)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class UserScoreEventConfiguration : IEntityTypeConfiguration<UserScoreEvent>
{
public void Configure(EntityTypeBuilder<UserScoreEvent> 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<Tenant>().WithMany()
.HasForeignKey(entity => entity.TenantId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class PracticeDailyUsageConfiguration : IEntityTypeConfiguration<PracticeDailyUsage>
{
public void Configure(EntityTypeBuilder<PracticeDailyUsage> 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<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class PracticeAccessEventConfiguration : IEntityTypeConfiguration<PracticeAccessEvent>
{
public void Configure(EntityTypeBuilder<PracticeAccessEvent> 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<Tenant>().WithMany()
.HasForeignKey(entity => entity.TenantId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<PracticeSession>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfiguration<PracticeSessionReport>
{
public void Configure(EntityTypeBuilder<PracticeSessionReport> 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.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<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PracticeSession>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PracticeBlueprint>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.BlueprintId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<QuestionCollection>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.CollectionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class PracticeSessionReportSectionConfiguration : IEntityTypeConfiguration<PracticeSessionReportSection>
{
public void Configure(EntityTypeBuilder<PracticeSessionReportSection> 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<PracticeSessionReport>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ReportId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PracticeSession>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class DashboardDailyStatConfiguration : IEntityTypeConfiguration<DashboardDailyStat>
{
public void Configure(EntityTypeBuilder<DashboardDailyStat> 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<Region>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class RevenueDailyStatConfiguration : IEntityTypeConfiguration<RevenueDailyStat>
{
public void Configure(EntityTypeBuilder<RevenueDailyStat> 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<Region>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}