forked from xiongyuxing/tiku-backend.net
feat: add content platform operations persistence
This commit is contained in:
@@ -41,6 +41,10 @@ public sealed class ContentAsset : AuditableTenantEntity
|
||||
public JsonElement VerificationDetails { get; set; } = JsonDefaults.Object();
|
||||
public string? PreviewObjectKey { get; set; }
|
||||
public AssetPreviewStatus PreviewStatus { get; set; } = AssetPreviewStatus.None;
|
||||
public AssetSecurityScanStatus SecurityScanStatus { get; set; } = AssetSecurityScanStatus.NotRequired;
|
||||
public DateTimeOffset? SecurityScannedAt { get; set; }
|
||||
public string? SecurityScanProvider { get; set; }
|
||||
public JsonElement SecurityScanSummary { get; set; } = JsonDefaults.Object();
|
||||
public JsonElement SecurityFlags { get; set; } = JsonDefaults.Object();
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
}
|
||||
@@ -189,6 +193,16 @@ public enum AssetPreviewStatus
|
||||
Failed
|
||||
}
|
||||
|
||||
public enum AssetSecurityScanStatus
|
||||
{
|
||||
NotRequired,
|
||||
Pending,
|
||||
Scanning,
|
||||
Passed,
|
||||
Failed,
|
||||
Skipped
|
||||
}
|
||||
|
||||
public enum ContentImportType
|
||||
{
|
||||
Questions,
|
||||
|
||||
98
Tiku.Domain/Content/ContentPlatformEntities.cs
Normal file
98
Tiku.Domain/Content/ContentPlatformEntities.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.Text.Json;
|
||||
using Tiku.Domain.Common;
|
||||
|
||||
namespace Tiku.Domain.Content;
|
||||
|
||||
public sealed class ContentAssetAccessEvent : Entity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid? AssetId { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public AssetAccessActorRole ActorRole { get; set; } = AssetAccessActorRole.Anonymous;
|
||||
public AssetAccessType AccessType { get; set; } = AssetAccessType.Preview;
|
||||
public string? Visibility { get; set; }
|
||||
public string? AssetType { get; set; }
|
||||
public string? StorageProvider { get; set; }
|
||||
public AssetAccessDisposition? Disposition { get; set; }
|
||||
public int? ExpiresInSeconds { get; set; }
|
||||
public string? SignatureMode { get; set; }
|
||||
public AssetAccessResult Result { get; set; } = AssetAccessResult.Granted;
|
||||
public string? DenyCode { get; set; }
|
||||
public string? IpAddress { get; set; }
|
||||
public string? UserAgent { get; set; }
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
public sealed class ContentAssetSecurityScanEvent : Entity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid? AssetId { get; set; }
|
||||
public string Provider { get; set; } = string.Empty;
|
||||
public AssetSecurityScanStatus ScanStatus { get; set; } = AssetSecurityScanStatus.Pending;
|
||||
public AssetSecurityRiskLevel RiskLevel { get; set; } = AssetSecurityRiskLevel.None;
|
||||
public string[] IssueCodes { get; set; } = [];
|
||||
public JsonElement Details { get; set; } = JsonDefaults.Object();
|
||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
public sealed class QuestionBankGrant : AuditableEntity
|
||||
{
|
||||
public Guid SourceQuestionBankId { get; set; }
|
||||
public Guid? CreatedBy { get; set; }
|
||||
public Guid? UpdatedBy { get; set; }
|
||||
public QuestionBankGrantScope GrantScope { get; set; } = QuestionBankGrantScope.Plans;
|
||||
public string[] AllowedPlanCodes { get; set; } = [];
|
||||
public Guid[] AllowedTenantIds { get; set; } = [];
|
||||
public Guid[] AllowedRegionIds { get; set; } = [];
|
||||
public Guid[] AllowedSubjectIds { get; set; } = [];
|
||||
public QuestionBankGrantStatus Status { get; set; } = QuestionBankGrantStatus.Active;
|
||||
public DateTimeOffset? StartsAt { get; set; }
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
}
|
||||
|
||||
public sealed class TenantQuestionBankAdoption : AuditableTenantEntity
|
||||
{
|
||||
public Guid SourceQuestionBankId { get; set; }
|
||||
public Guid? GrantId { get; set; }
|
||||
public Guid? TargetQuestionBankId { get; set; }
|
||||
public Guid? TargetEntryId { get; set; }
|
||||
public Guid? TargetCollectionId { get; set; }
|
||||
public Guid? CreatedBy { get; set; }
|
||||
public Guid? UpdatedBy { get; set; }
|
||||
public QuestionBankAdoptionMode AdoptionMode { get; set; } = QuestionBankAdoptionMode.CopiedSnapshot;
|
||||
public QuestionBankAdoptionStatus Status { get; set; } = QuestionBankAdoptionStatus.Active;
|
||||
public QuestionBankAdoptionSyncStatus SyncStatus { get; set; } = QuestionBankAdoptionSyncStatus.Synced;
|
||||
public JsonElement SourceSnapshot { get; set; } = JsonDefaults.Object();
|
||||
public int CopiedQuestionCount { get; set; }
|
||||
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
|
||||
public DateTimeOffset? LastSyncedAt { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AiRecommendationReport : AuditableTenantEntity
|
||||
{
|
||||
public Guid UserId { get; set; }
|
||||
public Guid? RegionId { get; set; }
|
||||
public AiRecommendationReportStatus Status { get; set; } = AiRecommendationReportStatus.Generated;
|
||||
public string Provider { get; set; } = "local_rules";
|
||||
public string? Model { get; set; }
|
||||
public string PromptVersion { get; set; } = "school-recommendation-v1";
|
||||
public JsonElement InputPayload { get; set; } = JsonDefaults.Object();
|
||||
public JsonElement ContextPayload { get; set; } = JsonDefaults.Object();
|
||||
public JsonElement ResultPayload { get; set; } = JsonDefaults.Object();
|
||||
public string? ErrorMessage { get; set; }
|
||||
public DateTimeOffset? GeneratedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum AssetAccessActorRole { Anonymous, Student, TenantAdmin, TenantContentEditor, System }
|
||||
public enum AssetAccessType { Download, Preview, AdminDownload, AdminPreview, UploadSign, UploadConfirm }
|
||||
public enum AssetAccessDisposition { Attachment, Inline }
|
||||
public enum AssetAccessResult { Granted, Denied }
|
||||
public enum AssetSecurityRiskLevel { None, Low, Medium, High, Critical }
|
||||
public enum QuestionBankGrantScope { AllActiveTenants, Plans, Tenants, Mixed }
|
||||
public enum QuestionBankGrantStatus { Active, Disabled, Expired }
|
||||
public enum QuestionBankAdoptionMode { CopiedSnapshot, Reference }
|
||||
public enum QuestionBankAdoptionStatus { Active, SyncPending, Suspended, Archived }
|
||||
public enum QuestionBankAdoptionSyncStatus { Pending, Synced, Failed }
|
||||
public enum AiRecommendationReportStatus { Draft, Generated, Failed }
|
||||
@@ -35,6 +35,11 @@ internal sealed class ContentAssetConfiguration : IEntityTypeConfiguration<Conte
|
||||
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();
|
||||
@@ -67,6 +72,14 @@ internal sealed class ContentAssetConfiguration : IEntityTypeConfiguration<Conte
|
||||
});
|
||||
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");
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.Content;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.QuestionBanks;
|
||||
using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class ContentAssetAccessEventConfiguration : IEntityTypeConfiguration<ContentAssetAccessEvent>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContentAssetAccessEvent> builder)
|
||||
{
|
||||
builder.ConfigureEntity("content_asset_access_events");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
|
||||
builder.Property(entity => entity.ActorRole).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.AccessType).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Visibility).HasMaxLength(50);
|
||||
builder.Property(entity => entity.AssetType).HasMaxLength(50);
|
||||
builder.Property(entity => entity.StorageProvider).HasMaxLength(50);
|
||||
builder.Property(entity => entity.Disposition).HasNullableSnakeCaseEnum();
|
||||
builder.Property(entity => entity.SignatureMode).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Result).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.DenyCode).HasMaxLength(100);
|
||||
builder.Property(entity => entity.IpAddress).HasMaxLength(100);
|
||||
builder.Property(entity => entity.UserAgent).HasMaxLength(1024);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.AssetId, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.CreatedAt })
|
||||
.HasFilter("user_id is not null");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.AccessType, entity.Result, entity.CreatedAt });
|
||||
builder.ToTable(table => table.HasCheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0"));
|
||||
builder.HasOne<Tenant>().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<ContentAsset>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.AssetId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ContentAssetSecurityScanEventConfiguration : IEntityTypeConfiguration<ContentAssetSecurityScanEvent>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContentAssetSecurityScanEvent> builder)
|
||||
{
|
||||
builder.ConfigureEntity("content_asset_security_scan_events");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
|
||||
builder.Property(entity => entity.Provider).HasMaxLength(100);
|
||||
builder.Property(entity => entity.ScanStatus).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.RiskLevel).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.IssueCodes)
|
||||
.HasColumnType("text[]")
|
||||
.HasDefaultValueSql("'{}'::text[]");
|
||||
builder.Property(entity => entity.Details).IsJson("{}");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.AssetId, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.ScanStatus, entity.RiskLevel, entity.CreatedAt });
|
||||
builder.HasOne<Tenant>().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<ContentAsset>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.AssetId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class QuestionBankGrantConfiguration : IEntityTypeConfiguration<QuestionBankGrant>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<QuestionBankGrant> builder)
|
||||
{
|
||||
builder.ConfigureEntity("question_bank_grants");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.GrantScope).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.AllowedPlanCodes)
|
||||
.HasColumnType("text[]")
|
||||
.HasDefaultValueSql("'{}'::text[]");
|
||||
builder.Property(entity => entity.AllowedTenantIds)
|
||||
.HasColumnType("uuid[]")
|
||||
.HasDefaultValueSql("'{}'::uuid[]");
|
||||
builder.Property(entity => entity.AllowedRegionIds)
|
||||
.HasColumnType("uuid[]")
|
||||
.HasDefaultValueSql("'{}'::uuid[]");
|
||||
builder.Property(entity => entity.AllowedSubjectIds)
|
||||
.HasColumnType("uuid[]")
|
||||
.HasDefaultValueSql("'{}'::uuid[]");
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.SourceQuestionBankId, entity.Status, entity.StartsAt, entity.ExpiresAt });
|
||||
builder.HasIndex(entity => entity.AllowedTenantIds).HasMethod("gin");
|
||||
builder.HasIndex(entity => entity.AllowedPlanCodes).HasMethod("gin");
|
||||
builder.HasOne<QuestionBank>().WithMany()
|
||||
.HasForeignKey(entity => entity.SourceQuestionBankId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.CreatedBy).OnDelete(DeleteBehavior.SetNull);
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.UpdatedBy).OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantQuestionBankAdoptionConfiguration : IEntityTypeConfiguration<TenantQuestionBankAdoption>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantQuestionBankAdoption> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_question_bank_adoptions");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.AdoptionMode).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.SyncStatus).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.SourceSnapshot).IsJson("{}");
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.SourceQuestionBankId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.UpdatedAt });
|
||||
builder.ToTable(table => table.HasCheckConstraint("ck_tenant_question_bank_adoptions_copied_count", "copied_question_count >= 0"));
|
||||
builder.HasOne<QuestionBank>().WithMany()
|
||||
.HasForeignKey(entity => entity.SourceQuestionBankId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<QuestionBankGrant>().WithMany()
|
||||
.HasForeignKey(entity => entity.GrantId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
builder.HasOne<QuestionBank>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.TargetQuestionBankId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<ContentEntry>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.TargetEntryId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<QuestionCollection>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.TargetCollectionId })
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AiRecommendationReportConfiguration : IEntityTypeConfiguration<AiRecommendationReport>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<AiRecommendationReport> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("ai_recommendation_reports");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Provider).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Model).HasMaxLength(100);
|
||||
builder.Property(entity => entity.PromptVersion).HasMaxLength(100);
|
||||
builder.Property(entity => entity.InputPayload).IsJson("{}");
|
||||
builder.Property(entity => entity.ContextPayload).IsJson("{}");
|
||||
builder.Property(entity => entity.ResultPayload).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.RegionId, entity.CreatedAt });
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Region>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
13838
Tiku.Infrastructure/Persistence/Migrations/20260725215750_AddContentPlatformOperationsPersistence.Designer.cs
generated
Normal file
13838
Tiku.Infrastructure/Persistence/Migrations/20260725215750_AddContentPlatformOperationsPersistence.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,452 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddContentPlatformOperationsPersistence : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "security_scan_provider",
|
||||
table: "content_assets",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "security_scan_status",
|
||||
table: "content_assets",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: false,
|
||||
defaultValue: "not_required");
|
||||
|
||||
migrationBuilder.AddColumn<JsonElement>(
|
||||
name: "security_scan_summary",
|
||||
table: "content_assets",
|
||||
type: "jsonb",
|
||||
nullable: false,
|
||||
defaultValueSql: "'{}'::jsonb");
|
||||
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "security_scanned_at",
|
||||
table: "content_assets",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ai_recommendation_reports",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
|
||||
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
region_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
provider = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
model = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
prompt_version = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
input_payload = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
context_payload = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
result_payload = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
error_message = table.Column<string>(type: "text", nullable: true),
|
||||
generated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_ai_recommendation_reports", x => x.id);
|
||||
table.UniqueConstraint("ak_ai_recommendation_reports_tenant_id_id", x => new { x.tenant_id, x.id });
|
||||
table.ForeignKey(
|
||||
name: "fk_ai_recommendation_reports_regions_tenant_id_region_id",
|
||||
columns: x => new { x.tenant_id, x.region_id },
|
||||
principalTable: "regions",
|
||||
principalColumns: new[] { "tenant_id", "id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_ai_recommendation_reports_tenants_tenant_id",
|
||||
column: x => x.tenant_id,
|
||||
principalTable: "tenants",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_ai_recommendation_reports_users_user_id",
|
||||
column: x => x.user_id,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "content_asset_access_events",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
asset_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
user_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
actor_role = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
access_type = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
visibility = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
|
||||
asset_type = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
|
||||
storage_provider = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
|
||||
disposition = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
expires_in_seconds = table.Column<int>(type: "integer", nullable: true),
|
||||
signature_mode = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
result = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
deny_code = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
ip_address = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
user_agent = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
metadata = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_content_asset_access_events", x => x.id);
|
||||
table.UniqueConstraint("ak_content_asset_access_events_tenant_id_id", x => new { x.tenant_id, x.id });
|
||||
table.CheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0");
|
||||
table.ForeignKey(
|
||||
name: "fk_content_asset_access_events_content_assets_tenant_id_asset_~",
|
||||
columns: x => new { x.tenant_id, x.asset_id },
|
||||
principalTable: "content_assets",
|
||||
principalColumns: new[] { "tenant_id", "id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_content_asset_access_events_tenants_tenant_id",
|
||||
column: x => x.tenant_id,
|
||||
principalTable: "tenants",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_content_asset_access_events_users_user_id",
|
||||
column: x => x.user_id,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "content_asset_security_scan_events",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
asset_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
provider = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
scan_status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
risk_level = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
issue_codes = table.Column<string[]>(type: "text[]", nullable: false, defaultValueSql: "'{}'::text[]"),
|
||||
details = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_content_asset_security_scan_events", x => x.id);
|
||||
table.UniqueConstraint("ak_content_asset_security_scan_events_tenant_id_id", x => new { x.tenant_id, x.id });
|
||||
table.ForeignKey(
|
||||
name: "fk_content_asset_security_scan_events_content_assets_tenant_id~",
|
||||
columns: x => new { x.tenant_id, x.asset_id },
|
||||
principalTable: "content_assets",
|
||||
principalColumns: new[] { "tenant_id", "id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_content_asset_security_scan_events_tenants_tenant_id",
|
||||
column: x => x.tenant_id,
|
||||
principalTable: "tenants",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "question_bank_grants",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
|
||||
source_question_bank_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_by = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
updated_by = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
grant_scope = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
allowed_plan_codes = table.Column<string[]>(type: "text[]", nullable: false, defaultValueSql: "'{}'::text[]"),
|
||||
allowed_tenant_ids = table.Column<Guid[]>(type: "uuid[]", nullable: false, defaultValueSql: "'{}'::uuid[]"),
|
||||
allowed_region_ids = table.Column<Guid[]>(type: "uuid[]", nullable: false, defaultValueSql: "'{}'::uuid[]"),
|
||||
allowed_subject_ids = table.Column<Guid[]>(type: "uuid[]", nullable: false, defaultValueSql: "'{}'::uuid[]"),
|
||||
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
starts_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
expires_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
metadata = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_question_bank_grants", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_question_bank_grants_question_banks_source_question_bank_id",
|
||||
column: x => x.source_question_bank_id,
|
||||
principalTable: "question_banks",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_question_bank_grants_users_created_by",
|
||||
column: x => x.created_by,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "fk_question_bank_grants_users_updated_by",
|
||||
column: x => x.updated_by,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "tenant_question_bank_adoptions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
|
||||
source_question_bank_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
grant_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
target_question_bank_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
target_entry_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
target_collection_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
created_by = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
updated_by = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
adoption_mode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
sync_status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
source_snapshot = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
copied_question_count = table.Column<int>(type: "integer", nullable: false),
|
||||
metadata = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
|
||||
last_synced_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_tenant_question_bank_adoptions", x => x.id);
|
||||
table.UniqueConstraint("ak_tenant_question_bank_adoptions_tenant_id_id", x => new { x.tenant_id, x.id });
|
||||
table.CheckConstraint("ck_tenant_question_bank_adoptions_copied_count", "copied_question_count >= 0");
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_content_entries_tenant_id_ta~",
|
||||
columns: x => new { x.tenant_id, x.target_entry_id },
|
||||
principalTable: "content_entries",
|
||||
principalColumns: new[] { "tenant_id", "id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_question_bank_grants_grant_id",
|
||||
column: x => x.grant_id,
|
||||
principalTable: "question_bank_grants",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_question_banks_source_questi~",
|
||||
column: x => x.source_question_bank_id,
|
||||
principalTable: "question_banks",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_question_banks_tenant_id_tar~",
|
||||
columns: x => new { x.tenant_id, x.target_question_bank_id },
|
||||
principalTable: "question_banks",
|
||||
principalColumns: new[] { "tenant_id", "id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_question_collections_tenant_~",
|
||||
columns: x => new { x.tenant_id, x.target_collection_id },
|
||||
principalTable: "question_collections",
|
||||
principalColumns: new[] { "tenant_id", "id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_tenants_tenant_id",
|
||||
column: x => x.tenant_id,
|
||||
principalTable: "tenants",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_users_created_by",
|
||||
column: x => x.created_by,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "fk_tenant_question_bank_adoptions_users_updated_by",
|
||||
column: x => x.updated_by,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_content_assets_tenant_id_security_scan_status_upload_status~",
|
||||
table: "content_assets",
|
||||
columns: new[] { "tenant_id", "security_scan_status", "upload_status", "status", "updated_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_ai_recommendation_reports_tenant_id_region_id_created_at",
|
||||
table: "ai_recommendation_reports",
|
||||
columns: new[] { "tenant_id", "region_id", "created_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_ai_recommendation_reports_tenant_id_user_id_created_at",
|
||||
table: "ai_recommendation_reports",
|
||||
columns: new[] { "tenant_id", "user_id", "created_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_ai_recommendation_reports_user_id",
|
||||
table: "ai_recommendation_reports",
|
||||
column: "user_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_content_asset_access_events_tenant_id_access_type_result_cr~",
|
||||
table: "content_asset_access_events",
|
||||
columns: new[] { "tenant_id", "access_type", "result", "created_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_content_asset_access_events_tenant_id_asset_id_created_at",
|
||||
table: "content_asset_access_events",
|
||||
columns: new[] { "tenant_id", "asset_id", "created_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_content_asset_access_events_tenant_id_user_id_created_at",
|
||||
table: "content_asset_access_events",
|
||||
columns: new[] { "tenant_id", "user_id", "created_at" },
|
||||
filter: "user_id is not null");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_content_asset_access_events_user_id",
|
||||
table: "content_asset_access_events",
|
||||
column: "user_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_content_asset_security_scan_events_tenant_id_asset_id_creat~",
|
||||
table: "content_asset_security_scan_events",
|
||||
columns: new[] { "tenant_id", "asset_id", "created_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_content_asset_security_scan_events_tenant_id_scan_status_ri~",
|
||||
table: "content_asset_security_scan_events",
|
||||
columns: new[] { "tenant_id", "scan_status", "risk_level", "created_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_question_bank_grants_allowed_plan_codes",
|
||||
table: "question_bank_grants",
|
||||
column: "allowed_plan_codes")
|
||||
.Annotation("Npgsql:IndexMethod", "gin");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_question_bank_grants_allowed_tenant_ids",
|
||||
table: "question_bank_grants",
|
||||
column: "allowed_tenant_ids")
|
||||
.Annotation("Npgsql:IndexMethod", "gin");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_question_bank_grants_created_by",
|
||||
table: "question_bank_grants",
|
||||
column: "created_by");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_question_bank_grants_source_question_bank_id_status_starts_~",
|
||||
table: "question_bank_grants",
|
||||
columns: new[] { "source_question_bank_id", "status", "starts_at", "expires_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_question_bank_grants_updated_by",
|
||||
table: "question_bank_grants",
|
||||
column: "updated_by");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_created_by",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
column: "created_by");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_grant_id",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
column: "grant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_source_question_bank_id",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
column: "source_question_bank_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_tenant_id_source_question_ba~",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
columns: new[] { "tenant_id", "source_question_bank_id" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_tenant_id_status_updated_at",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
columns: new[] { "tenant_id", "status", "updated_at" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_tenant_id_target_collection_~",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
columns: new[] { "tenant_id", "target_collection_id" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_tenant_id_target_entry_id",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
columns: new[] { "tenant_id", "target_entry_id" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_tenant_id_target_question_ba~",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
columns: new[] { "tenant_id", "target_question_bank_id" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tenant_question_bank_adoptions_updated_by",
|
||||
table: "tenant_question_bank_adoptions",
|
||||
column: "updated_by");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ai_recommendation_reports");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "content_asset_access_events");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "content_asset_security_scan_events");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "tenant_question_bank_adoptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "question_bank_grants");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_content_assets_tenant_id_security_scan_status_upload_status~",
|
||||
table: "content_assets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "security_scan_provider",
|
||||
table: "content_assets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "security_scan_status",
|
||||
table: "content_assets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "security_scan_summary",
|
||||
table: "content_assets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "security_scanned_at",
|
||||
table: "content_assets");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2816,6 +2816,105 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("tenant_usage_records", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("gen_random_uuid()");
|
||||
|
||||
b.Property<JsonElement>("ContextPayload")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("context_payload")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("error_message");
|
||||
|
||||
b.Property<DateTimeOffset?>("GeneratedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("generated_at");
|
||||
|
||||
b.Property<JsonElement>("InputPayload")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("input_payload")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("model");
|
||||
|
||||
b.Property<string>("PromptVersion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("prompt_version");
|
||||
|
||||
b.Property<string>("Provider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("provider");
|
||||
|
||||
b.Property<Guid?>("RegionId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("region_id");
|
||||
|
||||
b.Property<JsonElement>("ResultPayload")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("result_payload")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_ai_recommendation_reports");
|
||||
|
||||
b.HasAlternateKey("TenantId", "Id")
|
||||
.HasName("ak_ai_recommendation_reports_tenant_id_id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_ai_recommendation_reports_user_id");
|
||||
|
||||
b.HasIndex("TenantId", "RegionId", "CreatedAt")
|
||||
.HasDatabaseName("ix_ai_recommendation_reports_tenant_id_region_id_created_at");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "CreatedAt")
|
||||
.HasDatabaseName("ix_ai_recommendation_reports_tenant_id_user_id_created_at");
|
||||
|
||||
b.ToTable("ai_recommendation_reports", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -3015,6 +3114,29 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
.HasColumnName("security_flags")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<string>("SecurityScanProvider")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("security_scan_provider");
|
||||
|
||||
b.Property<string>("SecurityScanStatus")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasDefaultValue("not_required")
|
||||
.HasColumnName("security_scan_status");
|
||||
|
||||
b.Property<JsonElement>("SecurityScanSummary")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("security_scan_summary")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<DateTimeOffset?>("SecurityScannedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("security_scanned_at");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("sort_order");
|
||||
@@ -3140,6 +3262,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("TenantId", "UploadStatus", "Status", "UpdatedAt")
|
||||
.HasDatabaseName("ix_content_assets_tenant_id_upload_status_status_updated_at");
|
||||
|
||||
b.HasIndex("TenantId", "SecurityScanStatus", "UploadStatus", "Status", "UpdatedAt")
|
||||
.HasDatabaseName("ix_content_assets_tenant_id_security_scan_status_upload_status~");
|
||||
|
||||
b.HasIndex("TenantId", "Status", "Visibility", "AssetType", "RegionId", "SubjectId", "CategoryId", "SortOrder")
|
||||
.HasDatabaseName("ix_content_assets_tenant_id_status_visibility_asset_type_regio~");
|
||||
|
||||
@@ -3155,6 +3280,193 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("gen_random_uuid()");
|
||||
|
||||
b.Property<string>("AccessType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("access_type");
|
||||
|
||||
b.Property<string>("ActorRole")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("actor_role");
|
||||
|
||||
b.Property<Guid?>("AssetId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("asset_id");
|
||||
|
||||
b.Property<string>("AssetType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("asset_type");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<string>("DenyCode")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("deny_code");
|
||||
|
||||
b.Property<string>("Disposition")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("disposition");
|
||||
|
||||
b.Property<int?>("ExpiresInSeconds")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("expires_in_seconds");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("ip_address");
|
||||
|
||||
b.Property<JsonElement>("Metadata")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("metadata")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<string>("Result")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("result");
|
||||
|
||||
b.Property<string>("SignatureMode")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("signature_mode");
|
||||
|
||||
b.Property<string>("StorageProvider")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("storage_provider");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<string>("UserAgent")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("user_agent");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<string>("Visibility")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("visibility");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_content_asset_access_events");
|
||||
|
||||
b.HasAlternateKey("TenantId", "Id")
|
||||
.HasName("ak_content_asset_access_events_tenant_id_id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_content_asset_access_events_user_id");
|
||||
|
||||
b.HasIndex("TenantId", "AssetId", "CreatedAt")
|
||||
.HasDatabaseName("ix_content_asset_access_events_tenant_id_asset_id_created_at");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "CreatedAt")
|
||||
.HasDatabaseName("ix_content_asset_access_events_tenant_id_user_id_created_at")
|
||||
.HasFilter("user_id is not null");
|
||||
|
||||
b.HasIndex("TenantId", "AccessType", "Result", "CreatedAt")
|
||||
.HasDatabaseName("ix_content_asset_access_events_tenant_id_access_type_result_cr~");
|
||||
|
||||
b.ToTable("content_asset_access_events", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("gen_random_uuid()");
|
||||
|
||||
b.Property<Guid?>("AssetId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("asset_id");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<JsonElement>("Details")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("details")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.PrimitiveCollection<string[]>("IssueCodes")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text[]")
|
||||
.HasColumnName("issue_codes")
|
||||
.HasDefaultValueSql("'{}'::text[]");
|
||||
|
||||
b.Property<string>("Provider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("provider");
|
||||
|
||||
b.Property<string>("RiskLevel")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("risk_level");
|
||||
|
||||
b.Property<string>("ScanStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("scan_status");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_content_asset_security_scan_events");
|
||||
|
||||
b.HasAlternateKey("TenantId", "Id")
|
||||
.HasName("ak_content_asset_security_scan_events_tenant_id_id");
|
||||
|
||||
b.HasIndex("TenantId", "AssetId", "CreatedAt")
|
||||
.HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_asset_id_creat~");
|
||||
|
||||
b.HasIndex("TenantId", "ScanStatus", "RiskLevel", "CreatedAt")
|
||||
.HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_scan_status_ri~");
|
||||
|
||||
b.ToTable("content_asset_security_scan_events", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -4332,6 +4644,117 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.QuestionBankGrant", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("gen_random_uuid()");
|
||||
|
||||
b.PrimitiveCollection<string[]>("AllowedPlanCodes")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text[]")
|
||||
.HasColumnName("allowed_plan_codes")
|
||||
.HasDefaultValueSql("'{}'::text[]");
|
||||
|
||||
b.PrimitiveCollection<Guid[]>("AllowedRegionIds")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid[]")
|
||||
.HasColumnName("allowed_region_ids")
|
||||
.HasDefaultValueSql("'{}'::uuid[]");
|
||||
|
||||
b.PrimitiveCollection<Guid[]>("AllowedSubjectIds")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid[]")
|
||||
.HasColumnName("allowed_subject_ids")
|
||||
.HasDefaultValueSql("'{}'::uuid[]");
|
||||
|
||||
b.PrimitiveCollection<Guid[]>("AllowedTenantIds")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid[]")
|
||||
.HasColumnName("allowed_tenant_ids")
|
||||
.HasDefaultValueSql("'{}'::uuid[]");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<DateTimeOffset?>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expires_at");
|
||||
|
||||
b.Property<string>("GrantScope")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("grant_scope");
|
||||
|
||||
b.Property<JsonElement>("Metadata")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("metadata")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<Guid>("SourceQuestionBankId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("source_question_bank_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("StartsAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("starts_at");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("updated_by");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_question_bank_grants");
|
||||
|
||||
b.HasIndex("AllowedPlanCodes")
|
||||
.HasDatabaseName("ix_question_bank_grants_allowed_plan_codes");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("AllowedPlanCodes"), "gin");
|
||||
|
||||
b.HasIndex("AllowedTenantIds")
|
||||
.HasDatabaseName("ix_question_bank_grants_allowed_tenant_ids");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("AllowedTenantIds"), "gin");
|
||||
|
||||
b.HasIndex("CreatedBy")
|
||||
.HasDatabaseName("ix_question_bank_grants_created_by");
|
||||
|
||||
b.HasIndex("UpdatedBy")
|
||||
.HasDatabaseName("ix_question_bank_grants_updated_by");
|
||||
|
||||
b.HasIndex("SourceQuestionBankId", "Status", "StartsAt", "ExpiresAt")
|
||||
.HasDatabaseName("ix_question_bank_grants_source_question_bank_id_status_starts_~");
|
||||
|
||||
b.ToTable("question_bank_grants", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -4770,6 +5193,136 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("subject_shares", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankAdoption", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("gen_random_uuid()");
|
||||
|
||||
b.Property<string>("AdoptionMode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("adoption_mode");
|
||||
|
||||
b.Property<int>("CopiedQuestionCount")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("copied_question_count");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<Guid?>("GrantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("grant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("LastSyncedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_synced_at");
|
||||
|
||||
b.Property<JsonElement>("Metadata")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("metadata")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<Guid>("SourceQuestionBankId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("source_question_bank_id");
|
||||
|
||||
b.Property<JsonElement>("SourceSnapshot")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("source_snapshot")
|
||||
.HasDefaultValueSql("'{}'::jsonb");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<string>("SyncStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("sync_status");
|
||||
|
||||
b.Property<Guid?>("TargetCollectionId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("target_collection_id");
|
||||
|
||||
b.Property<Guid?>("TargetEntryId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("target_entry_id");
|
||||
|
||||
b.Property<Guid?>("TargetQuestionBankId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("target_question_bank_id");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at")
|
||||
.HasDefaultValueSql("now()");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("updated_by");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_tenant_question_bank_adoptions");
|
||||
|
||||
b.HasAlternateKey("TenantId", "Id")
|
||||
.HasName("ak_tenant_question_bank_adoptions_tenant_id_id");
|
||||
|
||||
b.HasIndex("CreatedBy")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_created_by");
|
||||
|
||||
b.HasIndex("GrantId")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_grant_id");
|
||||
|
||||
b.HasIndex("SourceQuestionBankId")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_source_question_bank_id");
|
||||
|
||||
b.HasIndex("UpdatedBy")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_updated_by");
|
||||
|
||||
b.HasIndex("TenantId", "SourceQuestionBankId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_tenant_id_source_question_ba~");
|
||||
|
||||
b.HasIndex("TenantId", "TargetCollectionId")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_tenant_id_target_collection_~");
|
||||
|
||||
b.HasIndex("TenantId", "TargetEntryId")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_tenant_id_target_entry_id");
|
||||
|
||||
b.HasIndex("TenantId", "TargetQuestionBankId")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_tenant_id_target_question_ba~");
|
||||
|
||||
b.HasIndex("TenantId", "Status", "UpdatedAt")
|
||||
.HasDatabaseName("ix_tenant_question_bank_adoptions_tenant_id_status_updated_at");
|
||||
|
||||
b.ToTable("tenant_question_bank_adoptions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_tenant_question_bank_adoptions_copied_count", "copied_question_count >= 0");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -11228,6 +11781,30 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
.HasConstraintName("fk_tenant_usage_records_tenants_tenant_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_ai_recommendation_reports_tenants_tenant_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_ai_recommendation_reports_users_user_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.Catalog.Region", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "RegionId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_ai_recommendation_reports_regions_tenant_id_region_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
|
||||
@@ -11294,6 +11871,46 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
.HasConstraintName("fk_content_assets_subjects_tenant_id_subject_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_content_asset_access_events_tenants_tenant_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.HasConstraintName("fk_content_asset_access_events_users_user_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.Content.ContentAsset", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "AssetId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_content_asset_access_events_content_assets_tenant_id_asset_~");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_content_asset_security_scan_events_tenants_tenant_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.Content.ContentAsset", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "AssetId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_content_asset_security_scan_events_content_assets_tenant_id~");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
@@ -11628,6 +12245,28 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
.HasConstraintName("fk_practice_blueprints_regions_tenant_id_region_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.QuestionBankGrant", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.HasConstraintName("fk_question_bank_grants_users_created_by");
|
||||
|
||||
b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SourceQuestionBankId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_question_bank_grants_question_banks_source_question_bank_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UpdatedBy")
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.HasConstraintName("fk_question_bank_grants_users_updated_by");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
@@ -11785,6 +12424,62 @@ namespace Tiku.Infrastructure.Persistence.Migrations
|
||||
.HasConstraintName("fk_subject_shares_subjects_tenant_id_target_subject_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankAdoption", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_users_created_by");
|
||||
|
||||
b.HasOne("Tiku.Domain.Content.QuestionBankGrant", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GrantId")
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_question_bank_grants_grant_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SourceQuestionBankId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_question_banks_source_questi~");
|
||||
|
||||
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_tenants_tenant_id");
|
||||
|
||||
b.HasOne("Tiku.Domain.Identity.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UpdatedBy")
|
||||
.OnDelete(DeleteBehavior.SetNull)
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_users_updated_by");
|
||||
|
||||
b.HasOne("Tiku.Domain.Content.QuestionCollection", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "TargetCollectionId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_question_collections_tenant_~");
|
||||
|
||||
b.HasOne("Tiku.Domain.Content.ContentEntry", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "TargetEntryId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_content_entries_tenant_id_ta~");
|
||||
|
||||
b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "TargetQuestionBankId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_tenant_question_bank_adoptions_question_banks_tenant_id_tar~");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b =>
|
||||
{
|
||||
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
|
||||
|
||||
@@ -57,6 +57,8 @@ public sealed class TikuDbContext(DbContextOptions<TikuDbContext> options) : DbC
|
||||
public DbSet<QuestionTypeGroup> QuestionTypeGroups => Set<QuestionTypeGroup>();
|
||||
public DbSet<SubjectShare> SubjectShares => Set<SubjectShare>();
|
||||
public DbSet<ContentAsset> ContentAssets => Set<ContentAsset>();
|
||||
public DbSet<ContentAssetAccessEvent> ContentAssetAccessEvents => Set<ContentAssetAccessEvent>();
|
||||
public DbSet<ContentAssetSecurityScanEvent> ContentAssetSecurityScanEvents => Set<ContentAssetSecurityScanEvent>();
|
||||
public DbSet<ContentImportJob> ContentImportJobs => Set<ContentImportJob>();
|
||||
public DbSet<ContentImportItem> ContentImportItems => Set<ContentImportItem>();
|
||||
public DbSet<ContentImportIssue> ContentImportIssues => Set<ContentImportIssue>();
|
||||
@@ -64,6 +66,9 @@ public sealed class TikuDbContext(DbContextOptions<TikuDbContext> options) : DbC
|
||||
public DbSet<AppAsset> AppAssets => Set<AppAsset>();
|
||||
public DbSet<VideoExplanation> VideoExplanations => Set<VideoExplanation>();
|
||||
public DbSet<QuestionVideo> QuestionVideos => Set<QuestionVideo>();
|
||||
public DbSet<QuestionBankGrant> QuestionBankGrants => Set<QuestionBankGrant>();
|
||||
public DbSet<TenantQuestionBankAdoption> TenantQuestionBankAdoptions => Set<TenantQuestionBankAdoption>();
|
||||
public DbSet<AiRecommendationReport> AiRecommendationReports => Set<AiRecommendationReport>();
|
||||
public DbSet<PracticeSession> PracticeSessions => Set<PracticeSession>();
|
||||
public DbSet<AnswerRecord> AnswerRecords => Set<AnswerRecord>();
|
||||
public DbSet<FavoriteQuestion> FavoriteQuestions => Set<FavoriteQuestion>();
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class PersistenceModelTests
|
||||
.Select(entity => entity.GetTableName())
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
Assert.Equal(106, tableNames.Count);
|
||||
Assert.Equal(111, tableNames.Count);
|
||||
Assert.Contains("tenants", tableNames);
|
||||
Assert.Contains("tenant_settings", tableNames);
|
||||
Assert.Contains("content_entries", tableNames);
|
||||
@@ -92,6 +92,11 @@ public sealed class PersistenceModelTests
|
||||
Assert.Contains("commerce_reconciliation_items", tableNames);
|
||||
Assert.Contains("commerce_reconciliation_issues", tableNames);
|
||||
Assert.Contains("commerce_reconciliation_issue_events", tableNames);
|
||||
Assert.Contains("content_asset_access_events", tableNames);
|
||||
Assert.Contains("content_asset_security_scan_events", tableNames);
|
||||
Assert.Contains("question_bank_grants", tableNames);
|
||||
Assert.Contains("tenant_question_bank_adoptions", tableNames);
|
||||
Assert.Contains("ai_recommendation_reports", tableNames);
|
||||
Assert.Contains("referral_tracks", tableNames);
|
||||
Assert.Contains("referral_codes", tableNames);
|
||||
Assert.Contains("referral_leads", tableNames);
|
||||
@@ -287,12 +292,21 @@ public sealed class PersistenceModelTests
|
||||
[Theory]
|
||||
[InlineData(typeof(ContentAsset), nameof(ContentAsset.AccessRules), "'{}'::jsonb")]
|
||||
[InlineData(typeof(ContentAsset), nameof(ContentAsset.VerificationDetails), "'{}'::jsonb")]
|
||||
[InlineData(typeof(ContentAsset), nameof(ContentAsset.SecurityScanSummary), "'{}'::jsonb")]
|
||||
[InlineData(typeof(ContentAsset), nameof(ContentAsset.SecurityFlags), "'{}'::jsonb")]
|
||||
[InlineData(typeof(ContentAssetAccessEvent), nameof(ContentAssetAccessEvent.Metadata), "'{}'::jsonb")]
|
||||
[InlineData(typeof(ContentAssetSecurityScanEvent), nameof(ContentAssetSecurityScanEvent.Details), "'{}'::jsonb")]
|
||||
[InlineData(typeof(ContentImportJob), nameof(ContentImportJob.RawPayload), "'[]'::jsonb")]
|
||||
[InlineData(typeof(ContentImportItem), nameof(ContentImportItem.SourcePayload), "'{}'::jsonb")]
|
||||
[InlineData(typeof(ContentImportIssue), nameof(ContentImportIssue.Details), "'{}'::jsonb")]
|
||||
[InlineData(typeof(VideoExplanation), nameof(VideoExplanation.KnowledgeTags), "'[]'::jsonb")]
|
||||
[InlineData(typeof(QuestionVideo), nameof(QuestionVideo.Metadata), "'{}'::jsonb")]
|
||||
[InlineData(typeof(QuestionBankGrant), nameof(QuestionBankGrant.Metadata), "'{}'::jsonb")]
|
||||
[InlineData(typeof(TenantQuestionBankAdoption), nameof(TenantQuestionBankAdoption.SourceSnapshot), "'{}'::jsonb")]
|
||||
[InlineData(typeof(TenantQuestionBankAdoption), nameof(TenantQuestionBankAdoption.Metadata), "'{}'::jsonb")]
|
||||
[InlineData(typeof(AiRecommendationReport), nameof(AiRecommendationReport.InputPayload), "'{}'::jsonb")]
|
||||
[InlineData(typeof(AiRecommendationReport), nameof(AiRecommendationReport.ContextPayload), "'{}'::jsonb")]
|
||||
[InlineData(typeof(AiRecommendationReport), nameof(AiRecommendationReport.ResultPayload), "'{}'::jsonb")]
|
||||
public void Asset_import_and_video_json_properties_are_mapped_to_jsonb(
|
||||
Type entityType,
|
||||
string propertyName,
|
||||
@@ -321,6 +335,17 @@ public sealed class PersistenceModelTests
|
||||
nameof(QuestionVideo.QuestionId),
|
||||
nameof(QuestionVideo.VideoId),
|
||||
nameof(QuestionVideo.VideoType));
|
||||
AssertHasUniqueIndex<TenantQuestionBankAdoption>(
|
||||
nameof(TenantQuestionBankAdoption.TenantId),
|
||||
nameof(TenantQuestionBankAdoption.SourceQuestionBankId));
|
||||
|
||||
var allowedPlanCodes = context.Model.FindEntityType(typeof(QuestionBankGrant))!
|
||||
.FindProperty(nameof(QuestionBankGrant.AllowedPlanCodes))!;
|
||||
Assert.Equal("text[]", allowedPlanCodes.GetColumnType());
|
||||
|
||||
var allowedTenantIds = context.Model.FindEntityType(typeof(QuestionBankGrant))!
|
||||
.FindProperty(nameof(QuestionBankGrant.AllowedTenantIds))!;
|
||||
Assert.Equal("uuid[]", allowedTenantIds.GetColumnType());
|
||||
|
||||
void AssertHasUniqueIndex<TEntity>(params string[] propertyNames)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user