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 { public void Configure(EntityTypeBuilder 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.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.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().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.SubjectId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.CategoryId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => entity.CreatedBy) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => entity.UpdatedBy) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => entity.VerifiedBy) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class ContentImportJobConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder 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().WithMany() .HasForeignKey(entity => entity.CreatedBy) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.TargetRegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.TargetSubjectId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.TargetCategoryId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.TargetContentNodeId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.TargetQuestionBankId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class ContentImportItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder 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.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().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.JobId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class ContentImportIssueConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder 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().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.JobId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ItemId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class ImageAssetConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder 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 { public void Configure(EntityTypeBuilder 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 { public void Configure(EntityTypeBuilder 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().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.SubjectId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class QuestionVideoConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder 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().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.QuestionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.VideoId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } }