forked from xiongyuxing/tiku-backend.net
295 lines
15 KiB
C#
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);
|
|
}
|
|
}
|