forked from gongxuegit/tiku-backend.net
feat: enforce tenant isolation and shared question bank
This commit is contained in:
@@ -182,7 +182,7 @@ internal sealed class ContentImportItemConfiguration : IEntityTypeConfiguration<
|
||||
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.RowNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.JobId, entity.Status, entity.RowNo });
|
||||
builder.ToTable(table =>
|
||||
{
|
||||
|
||||
@@ -134,7 +134,7 @@ internal sealed class PaymentConfiguration : IEntityTypeConfiguration<Payment>
|
||||
builder.Property(entity => entity.RawPayload).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.OrderId, entity.UpdatedAt });
|
||||
builder.HasIndex(entity => new { entity.Provider, entity.ProviderTradeNo })
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Provider, entity.ProviderTradeNo })
|
||||
.IsUnique()
|
||||
.HasFilter("provider_trade_no is not null");
|
||||
builder.ToTable(table =>
|
||||
@@ -160,7 +160,7 @@ internal sealed class PaymentEventConfiguration : IEntityTypeConfiguration<Payme
|
||||
builder.Property(entity => entity.EventId).HasMaxLength(200);
|
||||
builder.Property(entity => entity.Payload).IsJson("{}");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.Provider, entity.EventId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Provider, entity.EventId }).IsUnique();
|
||||
builder.HasOne<Tenant>().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Payment>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.PaymentId })
|
||||
@@ -438,7 +438,7 @@ internal sealed class CommerceReconciliationItemConfiguration : IEntityTypeConfi
|
||||
builder.Property(entity => entity.IssueCode).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Details).IsJson("{}");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.BatchId, entity.RowNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.BatchId, entity.RowNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.BatchId, entity.MatchStatus, entity.RowNo });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.OrderNo, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.RefundNo, entity.ProviderRefundNo, entity.CreatedAt });
|
||||
|
||||
@@ -180,7 +180,7 @@ internal sealed class QuestionCollectionItemConfiguration :
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.CollectionId,
|
||||
entity.QuestionId
|
||||
entity.QuestionReferenceId
|
||||
}).IsUnique();
|
||||
builder.HasIndex(entity => new
|
||||
{
|
||||
@@ -194,10 +194,14 @@ internal sealed class QuestionCollectionItemConfiguration :
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.CollectionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
builder.HasOne<TenantQuestionReference>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionReferenceId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.QuestionOwnerTenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,72 +65,48 @@ internal sealed class ContentAssetSecurityScanEventConfiguration : IEntityTypeCo
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class QuestionBankGrantConfiguration : IEntityTypeConfiguration<QuestionBankGrant>
|
||||
internal sealed class TenantQuestionBankPreferenceConfiguration : IEntityTypeConfiguration<TenantQuestionBankPreference>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<QuestionBankGrant> builder)
|
||||
public void Configure(EntityTypeBuilder<TenantQuestionBankPreference> builder)
|
||||
{
|
||||
builder.ConfigureEntity("question_bank_grants");
|
||||
builder.ConfigureTenantEntity("tenant_question_bank_preferences");
|
||||
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.Alias).HasMaxLength(300);
|
||||
builder.Property(entity => entity.NavigationLocation).HasMaxLength(100);
|
||||
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.HasIndex(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionBankOwnerTenantId,
|
||||
entity.QuestionBankId
|
||||
}).IsUnique();
|
||||
builder.HasOne<QuestionBank>().WithMany()
|
||||
.HasForeignKey(entity => entity.SourceQuestionBankId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.HasForeignKey(entity => new { entity.QuestionBankOwnerTenantId, entity.QuestionBankId })
|
||||
.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 TenantQuestionBankAdoptionConfiguration : IEntityTypeConfiguration<TenantQuestionBankAdoption>
|
||||
internal sealed class TenantQuestionReferenceConfiguration : IEntityTypeConfiguration<TenantQuestionReference>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantQuestionBankAdoption> builder)
|
||||
public void Configure(EntityTypeBuilder<TenantQuestionReference> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_question_bank_adoptions");
|
||||
builder.ConfigureTenantEntity("tenant_question_references");
|
||||
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 })
|
||||
builder.Property(entity => entity.Source).HasSnakeCaseEnum();
|
||||
builder.HasAlternateKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionOwnerTenantId,
|
||||
entity.QuestionId
|
||||
});
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.QuestionOwnerTenantId, entity.QuestionId })
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ internal sealed class PracticeSessionConfiguration : IEntityTypeConfiguration<Pr
|
||||
builder.Property(entity => entity.Mode).HasMaxLength(50);
|
||||
builder.Property(entity => entity.TargetType).HasMaxLength(50);
|
||||
builder.Property(entity => entity.StartedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(entity => entity.QuestionIds).IsJson("[]");
|
||||
builder.Property(entity => entity.TotalScore).HasPrecision(8, 2);
|
||||
builder.Property(entity => entity.AccessMode)
|
||||
.HasSnakeCaseEnum()
|
||||
@@ -55,6 +54,45 @@ internal sealed class PracticeSessionConfiguration : IEntityTypeConfiguration<Pr
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PracticeSessionQuestionConfiguration : IEntityTypeConfiguration<PracticeSessionQuestion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PracticeSessionQuestion> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("practice_session_questions");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.PracticeSessionId, entity.Id });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.PracticeSessionId, entity.Position }).IsUnique();
|
||||
builder.Property(entity => entity.Score).HasPrecision(8, 2);
|
||||
|
||||
builder.HasOne<PracticeSession>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<TenantQuestionReference>().WithMany()
|
||||
.HasForeignKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionOwnerTenantId,
|
||||
entity.QuestionId
|
||||
})
|
||||
.HasPrincipalKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionOwnerTenantId,
|
||||
entity.QuestionId
|
||||
})
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<QuestionVersion>().WithMany()
|
||||
.HasForeignKey(entity => new
|
||||
{
|
||||
TenantId = entity.QuestionOwnerTenantId,
|
||||
entity.QuestionId,
|
||||
Id = entity.QuestionVersionId
|
||||
})
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.QuestionId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AnswerRecordConfiguration : IEntityTypeConfiguration<AnswerRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<AnswerRecord> builder)
|
||||
@@ -67,13 +105,6 @@ internal sealed class AnswerRecordConfiguration : IEntityTypeConfiguration<Answe
|
||||
builder.Property(entity => entity.AnsweredAt).HasDefaultValueSql("now()");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.QuestionId });
|
||||
builder.HasIndex(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionId,
|
||||
entity.QuestionVersionId
|
||||
});
|
||||
builder.HasIndex(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
@@ -81,31 +112,9 @@ internal sealed class AnswerRecordConfiguration : IEntityTypeConfiguration<Answe
|
||||
entity.PracticeSessionId
|
||||
});
|
||||
|
||||
builder.ToTable(table => table.HasCheckConstraint(
|
||||
"ck_answer_records_version_requires_question",
|
||||
"question_version_id is null or question_id is not null"));
|
||||
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<QuestionVersion>().WithMany()
|
||||
.HasForeignKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionId,
|
||||
entity.QuestionVersionId
|
||||
})
|
||||
.HasPrincipalKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionId,
|
||||
entity.Id
|
||||
})
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<PracticeSession>().WithMany()
|
||||
.HasForeignKey(entity => new
|
||||
{
|
||||
@@ -120,6 +129,20 @@ internal sealed class AnswerRecordConfiguration : IEntityTypeConfiguration<Answe
|
||||
entity.Id
|
||||
})
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<PracticeSessionQuestion>().WithMany()
|
||||
.HasForeignKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.PracticeSessionId,
|
||||
Id = entity.SessionQuestionId
|
||||
})
|
||||
.HasPrincipalKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.PracticeSessionId,
|
||||
entity.Id
|
||||
})
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +151,7 @@ internal sealed class FavoriteQuestionConfiguration : IEntityTypeConfiguration<F
|
||||
public void Configure(EntityTypeBuilder<FavoriteQuestion> builder)
|
||||
{
|
||||
builder.ToTable("favorite_questions");
|
||||
builder.HasKey(entity => new { entity.TenantId, entity.UserId, entity.QuestionId });
|
||||
builder.HasKey(entity => new { entity.TenantId, entity.UserId, entity.QuestionReferenceId });
|
||||
builder.Property(entity => entity.Source).HasMaxLength(50);
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
|
||||
@@ -138,10 +161,14 @@ internal sealed class FavoriteQuestionConfiguration : IEntityTypeConfiguration<F
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
builder.HasOne<TenantQuestionReference>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionReferenceId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.QuestionOwnerTenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +177,7 @@ internal sealed class WrongQuestionConfiguration : IEntityTypeConfiguration<Wron
|
||||
public void Configure(EntityTypeBuilder<WrongQuestion> builder)
|
||||
{
|
||||
builder.ToTable("wrong_questions");
|
||||
builder.HasKey(entity => new { entity.TenantId, entity.UserId, entity.QuestionId });
|
||||
builder.HasKey(entity => new { entity.TenantId, entity.UserId, entity.QuestionReferenceId });
|
||||
builder.Property(entity => entity.WrongCount).HasDefaultValue(1);
|
||||
builder.Property(entity => entity.LastWrongAt).HasDefaultValueSql("now()");
|
||||
|
||||
@@ -160,10 +187,14 @@ internal sealed class WrongQuestionConfiguration : IEntityTypeConfiguration<Wron
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
builder.HasOne<TenantQuestionReference>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionReferenceId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.QuestionOwnerTenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ internal sealed class TenantInvoiceConfiguration : IEntityTypeConfiguration<Tena
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Currency).HasMaxLength(10);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => entity.InvoiceNo).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.InvoiceNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.DueDate });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.BillingPeriodStart, entity.BillingPeriodEnd })
|
||||
.IsUnique()
|
||||
@@ -110,7 +110,7 @@ internal sealed class TenantInvoicePaymentConfiguration : IEntityTypeConfigurati
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.ProviderTradeNo).HasMaxLength(200);
|
||||
builder.Property(entity => entity.RawPayload).IsJson("{}");
|
||||
builder.HasIndex(entity => entity.PaymentNo).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.PaymentNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.InvoiceId, entity.Status });
|
||||
builder.ToTable(table => table.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"));
|
||||
builder.HasOne<TenantInvoice>().WithMany()
|
||||
@@ -229,7 +229,7 @@ internal sealed class PlatformDunningNotificationEventConfiguration : IEntityTyp
|
||||
builder.Property(entity => entity.RequestPayload).IsJson("{}");
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.Property(entity => entity.ScheduledAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.ChannelId, entity.ReminderId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.ChannelId, entity.ReminderId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.Status, entity.NextAttemptAt, entity.ScheduledAt, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.ReminderId, entity.Status, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.InvoiceId, entity.Status, entity.CreatedAt });
|
||||
|
||||
@@ -14,7 +14,6 @@ internal sealed class QuestionBankConfiguration : IEntityTypeConfiguration<Quest
|
||||
builder.ConfigureTenantEntity("question_banks");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.Property(entity => entity.SourceScope).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
|
||||
@@ -94,7 +93,7 @@ internal sealed class QuestionVersionConfiguration : IEntityTypeConfiguration<Qu
|
||||
builder.Property(entity => entity.SourceHash).HasMaxLength(128);
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
|
||||
builder.HasIndex(entity => new { entity.QuestionId, entity.VersionNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.QuestionId, entity.VersionNo }).IsUnique();
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.QuestionBanks;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class TaxonomyNodeConfiguration : IEntityTypeConfiguration<TaxonomyNode>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TaxonomyNode> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("taxonomy_nodes");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.NodeType).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Code).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.Property(entity => entity.Path).HasColumnType("ltree");
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Code }).IsUnique();
|
||||
builder.HasOne<TaxonomyNode>().WithMany()
|
||||
.HasForeignKey(entity => new { TenantId = entity.ParentOwnerTenantId, Id = entity.ParentId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.ToTable(table => table.HasCheckConstraint(
|
||||
"ck_taxonomy_nodes_parent_pair",
|
||||
"(parent_owner_tenant_id is null) = (parent_id is null)"));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class QuestionTaxonomyAssignmentConfiguration : IEntityTypeConfiguration<QuestionTaxonomyAssignment>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<QuestionTaxonomyAssignment> builder)
|
||||
{
|
||||
builder.ConfigureEntity("question_taxonomy_assignments");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
|
||||
builder.HasIndex(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionId,
|
||||
entity.TaxonomyOwnerTenantId,
|
||||
entity.TaxonomyNodeId
|
||||
}).IsUnique();
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<TaxonomyNode>().WithMany()
|
||||
.HasForeignKey(entity => new { TenantId = entity.TaxonomyOwnerTenantId, Id = entity.TaxonomyNodeId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,9 @@ internal sealed class TenantConfiguration : IEntityTypeConfiguration<Tenant>
|
||||
|
||||
builder.HasIndex(entity => entity.Slug).IsUnique();
|
||||
builder.HasIndex(entity => entity.LegacyId).IsUnique();
|
||||
builder.HasIndex(entity => entity.Mode)
|
||||
.IsUnique()
|
||||
.HasFilter("mode = 'platform_owned'");
|
||||
|
||||
builder.HasOne<User>()
|
||||
.WithMany()
|
||||
@@ -67,7 +70,10 @@ internal sealed class TenantDomainConfiguration : IEntityTypeConfiguration<Tenan
|
||||
builder.Property(entity => entity.DomainType).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.VerificationToken).HasMaxLength(256);
|
||||
builder.HasIndex(entity => entity.Host).IsUnique();
|
||||
builder.Property(entity => entity.LastFailureReason).HasMaxLength(2000);
|
||||
builder.HasIndex(entity => entity.Host)
|
||||
.IsUnique()
|
||||
.HasAnnotation("Tiku:GlobalUnique", true);
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.IsPrimary })
|
||||
.IsUnique()
|
||||
.HasFilter("is_primary");
|
||||
@@ -114,3 +120,29 @@ internal sealed class TenantSettingsConfiguration : IEntityTypeConfiguration<Ten
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantFrontendConfigConfiguration : IEntityTypeConfiguration<TenantFrontendConfig>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantFrontendConfig> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_frontend_configs");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.HasIndex(entity => entity.TenantId).IsUnique();
|
||||
builder.Property(entity => entity.ConfigVersion).IsConcurrencyToken();
|
||||
builder.Property(entity => entity.PublishedBranding).IsJson("{}");
|
||||
builder.Property(entity => entity.PublishedTheme).IsJson("{}");
|
||||
builder.Property(entity => entity.PublishedFeatures).IsJson("{}");
|
||||
builder.Property(entity => entity.PublishedNavigation).IsJson("[]");
|
||||
builder.Property(entity => entity.PublishedHomeModules).IsJson("[]");
|
||||
builder.Property(entity => entity.DraftBranding).IsJson("{}");
|
||||
builder.Property(entity => entity.DraftTheme).IsJson("{}");
|
||||
builder.Property(entity => entity.DraftFeatures).IsJson("{}");
|
||||
builder.Property(entity => entity.DraftNavigation).IsJson("[]");
|
||||
builder.Property(entity => entity.DraftHomeModules).IsJson("[]");
|
||||
builder.ToTable(table =>
|
||||
{
|
||||
table.HasCheckConstraint("ck_tenant_frontend_configs_schema_version", "schema_version > 0");
|
||||
table.HasCheckConstraint("ck_tenant_frontend_configs_config_version", "config_version > 0");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,9 @@ internal sealed class AuthSessionConfiguration : IEntityTypeConfiguration<AuthSe
|
||||
builder.Property(entity => entity.IpAddress).HasMaxLength(64);
|
||||
builder.Property(entity => entity.UserAgent).HasMaxLength(1000);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => entity.TokenHash).IsUnique();
|
||||
builder.HasIndex(entity => entity.TokenHash)
|
||||
.IsUnique()
|
||||
.HasAnnotation("Tiku:GlobalUnique", true);
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.ExpiresAt })
|
||||
.HasFilter("revoked_at is null");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user