forked from gongxuegit/tiku-backend.net
feat: enforce tenant isolation and shared question bank
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user