Files
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

353 lines
18 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.QuestionBanks;
namespace Tiku.Infrastructure.Persistence.Configurations;
internal sealed class ContentAssetConfiguration : IEntityTypeConfiguration<ContentAsset>
{
public void Configure(EntityTypeBuilder<ContentAsset> builder)
{
builder.ConfigureTenantEntity("content_assets");
builder.ConfigureTimestamps();
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
builder.Property(entity => entity.AssetKey).HasMaxLength(200);
builder.Property(entity => entity.Title).HasMaxLength(300);
builder.Property(entity => entity.Category).HasMaxLength(100);
builder.Property(entity => entity.FileName).HasMaxLength(500);
builder.Property(entity => entity.CdnUrl).HasMaxLength(2048);
builder.Property(entity => entity.AssetType).HasSnakeCaseEnum();
builder.Property(entity => entity.StorageProvider).HasSnakeCaseEnum();
builder.Property(entity => entity.Bucket).HasMaxLength(200);
builder.Property(entity => entity.ObjectKey).HasMaxLength(1000);
builder.Property(entity => entity.MimeType).HasMaxLength(200);
builder.Property(entity => entity.ChecksumSha256).HasMaxLength(64);
builder.Property(entity => entity.Visibility).HasSnakeCaseEnum();
builder.Property(entity => entity.PreviewUrl).HasMaxLength(2048);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.AccessRules).IsJson("{}");
builder.Property(entity => entity.Source).HasMaxLength(50);
builder.Property(entity => entity.UploadStatus).HasSnakeCaseEnum();
builder.Property(entity => entity.VerifiedChecksumSha256).HasMaxLength(64);
builder.Property(entity => entity.VerificationDetails).IsJson("{}");
builder.Property(entity => entity.PreviewObjectKey).HasMaxLength(1000);
builder.Property(entity => entity.PreviewStatus).HasSnakeCaseEnum();
builder.Property(entity => entity.SecurityScanStatus)
.HasSnakeCaseEnum()
.HasDefaultValue(AssetSecurityScanStatus.NotRequired);
builder.Property(entity => entity.SecurityScanProvider).HasMaxLength(100);
builder.Property(entity => entity.SecurityScanSummary).IsJson("{}");
builder.Property(entity => entity.SecurityFlags).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.Visibility,
entity.AssetType,
entity.RegionId,
entity.SubjectId,
entity.CategoryId,
entity.SortOrder
});
builder.HasIndex(entity => new
{
entity.TenantId,
entity.StorageProvider,
entity.Bucket,
entity.ObjectKey
});
builder.HasIndex(entity => new { entity.TenantId, entity.ChecksumSha256 })
.HasFilter("checksum_sha256 is not null");
builder.HasIndex(entity => new
{
entity.TenantId,
entity.UploadStatus,
entity.Status,
entity.UpdatedAt
});
builder.HasIndex(entity => new { entity.TenantId, entity.PreviewStatus })
.HasFilter("preview_status <> 'none'");
builder.HasIndex(entity => new
{
entity.TenantId,
entity.SecurityScanStatus,
entity.UploadStatus,
entity.Status,
entity.UpdatedAt
});
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_content_assets_file_size", "file_size_bytes is null or file_size_bytes >= 0");
table.HasCheckConstraint("ck_content_assets_download_count", "download_count >= 0");
table.HasCheckConstraint("ck_content_assets_verified_size",
"verified_size_bytes is null or verified_size_bytes >= 0");
table.HasCheckConstraint(
"ck_content_assets_verified_checksum",
"verified_checksum_sha256 is null or verified_checksum_sha256 ~ '^[a-f0-9]{64}$'");
});
builder.HasOne<Region>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Subject>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.SubjectId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Category>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.CategoryId })
.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);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.CreatedBy)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UpdatedBy)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.VerifiedBy)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class ContentImportJobConfiguration : IEntityTypeConfiguration<ContentImportJob>
{
public void Configure(EntityTypeBuilder<ContentImportJob> builder)
{
builder.ConfigureTenantEntity("content_import_jobs");
builder.ConfigureTimestamps();
builder.Property(entity => entity.ImportType).HasSnakeCaseEnum();
builder.Property(entity => entity.SourceFormat).HasSnakeCaseEnum();
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.SourceName).HasMaxLength(500);
builder.Property(entity => entity.SourceHash).HasMaxLength(128);
builder.Property(entity => entity.Summary).IsJson("{}");
builder.Property(entity => entity.RawPayload).IsJson("[]");
builder.Property(entity => entity.NormalizedPayload).IsJson("[]");
builder.HasIndex(entity => new
{
entity.TenantId,
entity.ImportType,
entity.Status,
entity.CreatedAt
});
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_content_import_jobs_counts",
"total_count >= 0 and valid_count >= 0 and error_count >= 0 and warning_count >= 0 and inserted_count >= 0 and updated_count >= 0 and skipped_count >= 0");
});
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.CreatedBy)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<Region>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.TargetRegionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Subject>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.TargetSubjectId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Category>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.TargetCategoryId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentNode>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.TargetContentNodeId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<QuestionBank>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.TargetQuestionBankId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class ContentImportItemConfiguration : IEntityTypeConfiguration<ContentImportItem>
{
public void Configure(EntityTypeBuilder<ContentImportItem> builder)
{
builder.ConfigureTenantEntity("content_import_items");
builder.ConfigureTimestamps();
builder.Property(entity => entity.ExternalId).HasMaxLength(200);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.TargetType).HasMaxLength(100);
builder.Property(entity => entity.SourcePayload).IsJson("{}");
builder.Property(entity => entity.NormalizedPayload).IsJson("{}");
builder.Property(entity => entity.ContentHash).HasMaxLength(128);
builder.HasIndex(entity => new { entity.TenantId, entity.JobId, entity.RowNo }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.JobId, entity.Status, entity.RowNo });
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_content_import_items_row_no", "row_no >= 0");
table.HasCheckConstraint("ck_content_import_items_issues_count", "issues_count >= 0");
});
builder.HasOne<ContentImportJob>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.JobId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class ContentImportIssueConfiguration : IEntityTypeConfiguration<ContentImportIssue>
{
public void Configure(EntityTypeBuilder<ContentImportIssue> builder)
{
builder.ConfigureTenantEntity("content_import_issues");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Severity).HasSnakeCaseEnum();
builder.Property(entity => entity.Code).HasMaxLength(100);
builder.Property(entity => entity.FieldPath).HasMaxLength(500);
builder.Property(entity => entity.Message).HasMaxLength(2000);
builder.Property(entity => entity.Details).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.JobId, entity.Severity, entity.RowNo });
builder.HasOne<ContentImportJob>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.JobId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<ContentImportItem>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ItemId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class ImageAssetConfiguration : IEntityTypeConfiguration<ImageAsset>
{
public void Configure(EntityTypeBuilder<ImageAsset> builder)
{
builder.ConfigureTenantEntity("images");
builder.ConfigureTimestamps();
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
builder.Property(entity => entity.Title).HasMaxLength(300);
builder.Property(entity => entity.Category).HasMaxLength(100);
builder.Property(entity => entity.FileName).HasMaxLength(500);
builder.Property(entity => entity.CdnUrl).HasMaxLength(2048);
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Category, entity.IsPublic });
}
}
internal sealed class AppAssetConfiguration : IEntityTypeConfiguration<AppAsset>
{
public void Configure(EntityTypeBuilder<AppAsset> builder)
{
builder.ConfigureTenantEntity("app_assets");
builder.ConfigureTimestamps();
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
builder.Property(entity => entity.AssetKey).HasMaxLength(200);
builder.Property(entity => entity.FileName).HasMaxLength(500);
builder.Property(entity => entity.Description).HasMaxLength(1000);
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.AssetKey }).IsUnique();
}
}
internal sealed class VideoExplanationConfiguration : IEntityTypeConfiguration<VideoExplanation>
{
public void Configure(EntityTypeBuilder<VideoExplanation> builder)
{
builder.ConfigureTenantEntity("video_explanations");
builder.ConfigureTimestamps();
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
builder.Property(entity => entity.LegacySubjectId).HasMaxLength(64);
builder.Property(entity => entity.Title).HasMaxLength(300);
builder.Property(entity => entity.VideoUrl).HasMaxLength(2048);
builder.Property(entity => entity.ThumbnailUrl).HasMaxLength(2048);
builder.Property(entity => entity.KnowledgeTags).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.SourceHash })
.HasFilter("source_hash is not null");
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.Restrict);
}
}
internal sealed class QuestionVideoConfiguration : IEntityTypeConfiguration<QuestionVideo>
{
public void Configure(EntityTypeBuilder<QuestionVideo> builder)
{
builder.ConfigureTenantEntity("question_videos");
builder.ConfigureTimestamps();
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
builder.Property(entity => entity.LegacyQuestionId).HasMaxLength(64);
builder.Property(entity => entity.LegacyVideoId).HasMaxLength(64);
builder.Property(entity => entity.VideoType).HasSnakeCaseEnum();
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
builder.HasIndex(entity => new
{
entity.TenantId,
entity.QuestionId,
entity.VideoId,
entity.VideoType
}).IsUnique();
builder.HasOne<Question>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<VideoExplanation>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.VideoId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class VideoPlaybackProgressConfiguration : IEntityTypeConfiguration<VideoPlaybackProgress>
{
public void Configure(EntityTypeBuilder<VideoPlaybackProgress> builder)
{
builder.ConfigureTenantEntity("video_playback_progress");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.Property(entity => entity.LastPlayedAt).HasDefaultValueSql("now()");
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.VideoId })
.IsUnique()
.HasFilter("question_id is null");
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.VideoId, entity.QuestionId })
.IsUnique()
.HasFilter("question_id is not null");
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.LastPlayedAt });
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<VideoExplanation>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.VideoId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<Question>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_video_playback_progress_position", "position_seconds >= 0");
table.HasCheckConstraint("ck_video_playback_progress_duration",
"duration_seconds is null or duration_seconds >= 0");
table.HasCheckConstraint("ck_video_playback_progress_watched", "watched_seconds >= 0");
table.HasCheckConstraint("ck_video_playback_progress_play_count", "play_count >= 0");
});
}
}