refactor(learning): replace legacy session targets

This commit is contained in:
2026-08-06 10:16:45 +08:00
parent 6e3b746e26
commit 61164f665c
13 changed files with 26796 additions and 175 deletions

View File

@@ -46,11 +46,6 @@ public sealed class PracticeSessionQueryDto
/// </summary>
public Guid? PracticeSessionId { get; set; }
/// <summary>
/// 练习蓝图 ID。
/// </summary>
public Guid? BlueprintId { get; set; }
/// <summary>
/// 模式。
/// </summary>
@@ -73,7 +68,6 @@ public sealed class PracticeSessionQueryDto
{
return new PracticeSessionFilter(
routePracticeSessionId ?? PracticeSessionId,
BlueprintId,
Mode,
Status,
Limit);

View File

@@ -105,7 +105,6 @@ public sealed record PracticeSessionCommand(
public sealed record PracticeSessionFilter(
Guid? PracticeSessionId = null,
Guid? BlueprintId = null,
string? Mode = null,
string? Status = null,
int? Limit = null);
@@ -167,12 +166,8 @@ public sealed record LearningActionResult(bool Ok, bool? IsFavorite = null);
public sealed record PracticeSessionItem(
Guid Id,
string Mode,
string? TargetType,
Guid? TargetId,
Guid? BlueprintId,
Guid? CollectionId,
Guid? EntryId,
Guid? ContentNodeId,
AccessResourceType ResourceType,
Guid ResourceId,
int QuestionCount,
int? DurationMinutes,
decimal? TotalScore,
@@ -215,8 +210,8 @@ public sealed record PracticeSessionQuestionItem(
public sealed record PracticeSessionReportItem(
Guid Id,
Guid PracticeSessionId,
Guid? BlueprintId,
Guid? CollectionId,
AccessResourceType ResourceType,
Guid ResourceId,
string Mode,
int TotalQuestions,
int AnsweredCount,
@@ -242,12 +237,8 @@ public sealed record PracticeSessionReportItem(
public sealed record PracticeHistoryItem(
Guid Id,
string Mode,
string? TargetType,
Guid? TargetId,
Guid? BlueprintId,
Guid? CollectionId,
Guid? EntryId,
Guid? ContentNodeId,
AccessResourceType ResourceType,
Guid ResourceId,
int QuestionCount,
int AnsweredCount,
int CorrectCount,

View File

@@ -6,13 +6,9 @@ namespace Tiku.Domain.Learning;
public sealed class PracticeSession : TenantEntity
{
public Guid UserId { get; set; }
public Guid? BlueprintId { get; set; }
public Guid? CollectionId { get; set; }
public Guid? EntryId { get; set; }
public Guid? ContentNodeId { get; set; }
public AccessResourceType ResourceType { get; set; }
public Guid ResourceId { get; set; }
public string Mode { get; set; } = "chapter";
public string? TargetType { get; set; }
public Guid? TargetId { get; set; }
public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? FinishedAt { get; set; }
public int QuestionCount { get; set; }

View File

@@ -95,8 +95,8 @@ public sealed class PracticeSessionReport : AuditableTenantEntity
{
public Guid UserId { get; set; }
public Guid PracticeSessionId { get; set; }
public Guid? BlueprintId { get; set; }
public Guid? CollectionId { get; set; }
public AccessResourceType ResourceType { get; set; }
public Guid ResourceId { get; set; }
public string Mode { get; set; } = "mock_exam";
public int TotalQuestions { get; set; }
public int AnsweredCount { get; set; }

View File

@@ -237,8 +237,8 @@ internal abstract partial class LearningActivityServiceBase
TenantId = actor.TenantId,
UserId = actor.UserId,
PracticeSessionId = session.Id,
BlueprintId = session.BlueprintId,
CollectionId = session.CollectionId,
ResourceType = session.ResourceType,
ResourceId = session.ResourceId,
Mode = session.Mode,
TotalQuestions = totalQuestions,
AnsweredCount = answeredCount,
@@ -358,12 +358,8 @@ internal abstract partial class LearningActivityServiceBase
return new PracticeSessionItem(
item.Id,
item.Mode,
item.TargetType,
item.TargetId,
item.BlueprintId,
item.CollectionId,
item.EntryId,
item.ContentNodeId,
item.ResourceType,
item.ResourceId,
item.QuestionCount,
item.DurationMinutes,
item.TotalScore,
@@ -389,8 +385,8 @@ internal abstract partial class LearningActivityServiceBase
return new PracticeSessionReportItem(
item.Id,
item.PracticeSessionId,
item.BlueprintId,
item.CollectionId,
item.ResourceType,
item.ResourceId,
item.Mode,
item.TotalQuestions,
item.AnsweredCount,

View File

@@ -46,8 +46,6 @@ internal sealed class PracticeReportService(LearningServiceDependencies dependen
report.TenantId == actor.TenantId &&
report.UserId == actor.UserId);
if (filter.BlueprintId.HasValue) query = query.Where(report => report.BlueprintId == filter.BlueprintId.Value);
if (!string.IsNullOrWhiteSpace(filter.Mode)) query = query.Where(report => report.Mode == filter.Mode.Trim());
var items = await query
@@ -85,12 +83,8 @@ internal sealed class PracticeReportService(LearningServiceDependencies dependen
.Select(row => new PracticeHistoryItem(
row.session.Id,
row.session.Mode,
row.session.TargetType,
row.session.TargetId,
row.session.BlueprintId,
row.session.CollectionId,
row.session.EntryId,
row.session.ContentNodeId,
row.session.ResourceType,
row.session.ResourceId,
row.session.QuestionCount,
row.report?.AnsweredCount ?? 0,
row.report?.CorrectCount ?? 0,

View File

@@ -137,11 +137,8 @@ internal sealed class V2PracticeSessionService(LearningServiceDependencies depen
TenantId = actor.TenantId,
UserId = actor.UserId,
Mode = mode,
TargetType = resourceType.ToString(),
TargetId = resourceId,
BlueprintId = resourceType == AccessResourceType.BlueprintRelease ? resourceId : null,
CollectionId = resourceType == AccessResourceType.CollectionRelease ? resourceId : null,
ContentNodeId = resourceType == AccessResourceType.CurriculumNode ? resourceId : null,
ResourceType = resourceType,
ResourceId = resourceId,
QuestionCount = distinctCandidates.Length,
DurationMinutes = durationMinutes,
TotalScore = totalScore,

View File

@@ -14,7 +14,7 @@ internal sealed class PracticeSessionConfiguration : IEntityTypeConfiguration<Pr
builder.ConfigureTenantEntity("practice_sessions");
builder.HasAlternateKey(entity => new { entity.TenantId, entity.UserId, entity.Id });
builder.Property(entity => entity.Mode).HasMaxLength(50);
builder.Property(entity => entity.TargetType).HasMaxLength(50);
builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum();
builder.Property(entity => entity.StartedAt).HasDefaultValueSql("now()");
builder.Property(entity => entity.TotalScore).HasPrecision(8, 2);
builder.Property(entity => entity.Status).HasSnakeCaseEnum().HasDefaultValue(PracticeSessionStatus.Active);
@@ -38,22 +38,6 @@ internal sealed class PracticeSessionConfiguration : IEntityTypeConfiguration<Pr
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PracticeBlueprint>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.BlueprintId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<QuestionCollection>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.CollectionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentEntry>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.EntryId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ContentNode>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ContentNodeId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}

View File

@@ -185,6 +185,7 @@ internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfigurat
builder.ConfigureTenantEntity("practice_session_reports");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Mode).HasMaxLength(50);
builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum();
builder.Property(entity => entity.Score).HasPrecision(10, 2);
builder.Property(entity => entity.TotalScore).HasPrecision(10, 2);
builder.Property(entity => entity.Accuracy).HasPrecision(6, 4);
@@ -199,8 +200,7 @@ internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfigurat
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.PracticeSessionId }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.SubmittedAt });
builder.HasIndex(entity => new { entity.TenantId, entity.BlueprintId, entity.SubmittedAt })
.HasFilter("blueprint_id is not null");
builder.HasIndex(entity => new { entity.TenantId, entity.ResourceType, entity.ResourceId, entity.SubmittedAt });
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_practice_session_reports_counts",
@@ -217,14 +217,6 @@ internal sealed class PracticeSessionReportConfiguration : IEntityTypeConfigurat
.HasForeignKey(entity => new { entity.TenantId, entity.PracticeSessionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PracticeBlueprint>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.BlueprintId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<QuestionCollection>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.CollectionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -299,4 +291,4 @@ internal sealed class RevenueDailyStatConfiguration : IEntityTypeConfiguration<R
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
}
}
}

View File

@@ -0,0 +1,282 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tiku.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class MigratePracticeSessionResourcesV2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_practice_session_reports_practice_blueprints_tenant_id_blue~",
table: "practice_session_reports");
migrationBuilder.DropForeignKey(
name: "fk_practice_session_reports_question_collections_tenant_id_col~",
table: "practice_session_reports");
migrationBuilder.DropForeignKey(
name: "fk_practice_sessions_content_entries_tenant_id_entry_id",
table: "practice_sessions");
migrationBuilder.DropForeignKey(
name: "fk_practice_sessions_content_nodes_tenant_id_content_node_id",
table: "practice_sessions");
migrationBuilder.DropForeignKey(
name: "fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id",
table: "practice_sessions");
migrationBuilder.DropForeignKey(
name: "fk_practice_sessions_question_collections_tenant_id_collection~",
table: "practice_sessions");
migrationBuilder.DropIndex(
name: "ix_practice_sessions_tenant_id_blueprint_id",
table: "practice_sessions");
migrationBuilder.DropIndex(
name: "ix_practice_sessions_tenant_id_collection_id",
table: "practice_sessions");
migrationBuilder.DropIndex(
name: "ix_practice_sessions_tenant_id_content_node_id",
table: "practice_sessions");
migrationBuilder.DropIndex(
name: "ix_practice_sessions_tenant_id_entry_id",
table: "practice_sessions");
migrationBuilder.DropIndex(
name: "ix_practice_session_reports_tenant_id_blueprint_id_submitted_at",
table: "practice_session_reports");
migrationBuilder.DropIndex(
name: "ix_practice_session_reports_tenant_id_collection_id",
table: "practice_session_reports");
migrationBuilder.DropColumn(
name: "blueprint_id",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "collection_id",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "content_node_id",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "entry_id",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "target_id",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "target_type",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "blueprint_id",
table: "practice_session_reports");
migrationBuilder.DropColumn(
name: "collection_id",
table: "practice_session_reports");
migrationBuilder.AddColumn<Guid>(
name: "resource_id",
table: "practice_sessions",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn<string>(
name: "resource_type",
table: "practice_sessions",
type: "character varying(32)",
maxLength: 32,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<Guid>(
name: "resource_id",
table: "practice_session_reports",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn<string>(
name: "resource_type",
table: "practice_session_reports",
type: "character varying(32)",
maxLength: 32,
nullable: false,
defaultValue: "");
migrationBuilder.CreateIndex(
name: "ix_practice_session_reports_tenant_id_resource_type_resource_i~",
table: "practice_session_reports",
columns: new[] { "tenant_id", "resource_type", "resource_id", "submitted_at" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "ix_practice_session_reports_tenant_id_resource_type_resource_i~",
table: "practice_session_reports");
migrationBuilder.DropColumn(
name: "resource_id",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "resource_type",
table: "practice_sessions");
migrationBuilder.DropColumn(
name: "resource_id",
table: "practice_session_reports");
migrationBuilder.DropColumn(
name: "resource_type",
table: "practice_session_reports");
migrationBuilder.AddColumn<Guid>(
name: "blueprint_id",
table: "practice_sessions",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "collection_id",
table: "practice_sessions",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "content_node_id",
table: "practice_sessions",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "entry_id",
table: "practice_sessions",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "target_id",
table: "practice_sessions",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "target_type",
table: "practice_sessions",
type: "character varying(50)",
maxLength: 50,
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "blueprint_id",
table: "practice_session_reports",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "collection_id",
table: "practice_session_reports",
type: "uuid",
nullable: true);
migrationBuilder.CreateIndex(
name: "ix_practice_sessions_tenant_id_blueprint_id",
table: "practice_sessions",
columns: new[] { "tenant_id", "blueprint_id" });
migrationBuilder.CreateIndex(
name: "ix_practice_sessions_tenant_id_collection_id",
table: "practice_sessions",
columns: new[] { "tenant_id", "collection_id" });
migrationBuilder.CreateIndex(
name: "ix_practice_sessions_tenant_id_content_node_id",
table: "practice_sessions",
columns: new[] { "tenant_id", "content_node_id" });
migrationBuilder.CreateIndex(
name: "ix_practice_sessions_tenant_id_entry_id",
table: "practice_sessions",
columns: new[] { "tenant_id", "entry_id" });
migrationBuilder.CreateIndex(
name: "ix_practice_session_reports_tenant_id_blueprint_id_submitted_at",
table: "practice_session_reports",
columns: new[] { "tenant_id", "blueprint_id", "submitted_at" },
filter: "blueprint_id is not null");
migrationBuilder.CreateIndex(
name: "ix_practice_session_reports_tenant_id_collection_id",
table: "practice_session_reports",
columns: new[] { "tenant_id", "collection_id" });
migrationBuilder.AddForeignKey(
name: "fk_practice_session_reports_practice_blueprints_tenant_id_blue~",
table: "practice_session_reports",
columns: new[] { "tenant_id", "blueprint_id" },
principalTable: "practice_blueprints",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "fk_practice_session_reports_question_collections_tenant_id_col~",
table: "practice_session_reports",
columns: new[] { "tenant_id", "collection_id" },
principalTable: "question_collections",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "fk_practice_sessions_content_entries_tenant_id_entry_id",
table: "practice_sessions",
columns: new[] { "tenant_id", "entry_id" },
principalTable: "content_entries",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "fk_practice_sessions_content_nodes_tenant_id_content_node_id",
table: "practice_sessions",
columns: new[] { "tenant_id", "content_node_id" },
principalTable: "content_nodes",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id",
table: "practice_sessions",
columns: new[] { "tenant_id", "blueprint_id" },
principalTable: "practice_blueprints",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "fk_practice_sessions_question_collections_tenant_id_collection~",
table: "practice_sessions",
columns: new[] { "tenant_id", "collection_id" },
principalTable: "question_collections",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Restrict);
}
}
}

View File

@@ -12281,30 +12281,14 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("authorization_expires_at");
b.Property<Guid?>("BlueprintId")
.HasColumnType("uuid")
.HasColumnName("blueprint_id");
b.Property<Guid?>("CollectionId")
.HasColumnType("uuid")
.HasColumnName("collection_id");
b.Property<int>("ConsumedFreeQuota")
.HasColumnType("integer")
.HasColumnName("consumed_free_quota");
b.Property<Guid?>("ContentNodeId")
.HasColumnType("uuid")
.HasColumnName("content_node_id");
b.Property<int?>("DurationMinutes")
.HasColumnType("integer")
.HasColumnName("duration_minutes");
b.Property<Guid?>("EntryId")
.HasColumnType("uuid")
.HasColumnName("entry_id");
b.Property<DateTimeOffset?>("ExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires_at");
@@ -12333,6 +12317,16 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("integer")
.HasColumnName("question_count");
b.Property<Guid>("ResourceId")
.HasColumnType("uuid")
.HasColumnName("resource_id");
b.Property<string>("ResourceType")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("resource_type");
b.Property<DateTimeOffset>("StartedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
@@ -12351,15 +12345,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("bigint")
.HasColumnName("strong_revocation_version");
b.Property<Guid?>("TargetId")
.HasColumnType("uuid")
.HasColumnName("target_id");
b.Property<string>("TargetType")
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("target_type");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
@@ -12392,18 +12377,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasIndex("UserId")
.HasDatabaseName("ix_practice_sessions_user_id");
b.HasIndex("TenantId", "BlueprintId")
.HasDatabaseName("ix_practice_sessions_tenant_id_blueprint_id");
b.HasIndex("TenantId", "CollectionId")
.HasDatabaseName("ix_practice_sessions_tenant_id_collection_id");
b.HasIndex("TenantId", "ContentNodeId")
.HasDatabaseName("ix_practice_sessions_tenant_id_content_node_id");
b.HasIndex("TenantId", "EntryId")
.HasDatabaseName("ix_practice_sessions_tenant_id_entry_id");
b.HasIndex("TenantId", "UserId", "StartedAt")
.HasDatabaseName("ix_practice_sessions_tenant_id_user_id_started_at");
@@ -12555,14 +12528,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("integer")
.HasColumnName("answered_count");
b.Property<Guid?>("BlueprintId")
.HasColumnType("uuid")
.HasColumnName("blueprint_id");
b.Property<Guid?>("CollectionId")
.HasColumnType("uuid")
.HasColumnName("collection_id");
b.Property<int>("CorrectCount")
.HasColumnType("integer")
.HasColumnName("correct_count");
@@ -12609,6 +12574,16 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnName("question_results")
.HasDefaultValueSql("'[]'::jsonb");
b.Property<Guid>("ResourceId")
.HasColumnType("uuid")
.HasColumnName("resource_id");
b.Property<string>("ResourceType")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("resource_type");
b.Property<decimal>("Score")
.HasPrecision(10, 2)
.HasColumnType("numeric(10,2)")
@@ -12696,20 +12671,16 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasIndex("UserId")
.HasDatabaseName("ix_practice_session_reports_user_id");
b.HasIndex("TenantId", "CollectionId")
.HasDatabaseName("ix_practice_session_reports_tenant_id_collection_id");
b.HasIndex("TenantId", "PracticeSessionId")
.IsUnique()
.HasDatabaseName("ix_practice_session_reports_tenant_id_practice_session_id");
b.HasIndex("TenantId", "BlueprintId", "SubmittedAt")
.HasDatabaseName("ix_practice_session_reports_tenant_id_blueprint_id_submitted_at")
.HasFilter("blueprint_id is not null");
b.HasIndex("TenantId", "UserId", "SubmittedAt")
.HasDatabaseName("ix_practice_session_reports_tenant_id_user_id_submitted_at");
b.HasIndex("TenantId", "ResourceType", "ResourceId", "SubmittedAt")
.HasDatabaseName("ix_practice_session_reports_tenant_id_resource_type_resource_i~");
b.ToTable("practice_session_reports", null, t =>
{
t.HasCheckConstraint("ck_practice_session_reports_accuracy", "accuracy >= 0 and accuracy <= 1");
@@ -24557,34 +24528,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_practice_sessions_users_user_id");
b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null)
.WithMany()
.HasForeignKey("TenantId", "BlueprintId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id");
b.HasOne("Tiku.Domain.Content.QuestionCollection", null)
.WithMany()
.HasForeignKey("TenantId", "CollectionId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_practice_sessions_question_collections_tenant_id_collection~");
b.HasOne("Tiku.Domain.Content.ContentNode", null)
.WithMany()
.HasForeignKey("TenantId", "ContentNodeId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_practice_sessions_content_nodes_tenant_id_content_node_id");
b.HasOne("Tiku.Domain.Content.ContentEntry", null)
.WithMany()
.HasForeignKey("TenantId", "EntryId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_practice_sessions_content_entries_tenant_id_entry_id");
});
modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b =>
@@ -24661,20 +24604,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.IsRequired()
.HasConstraintName("fk_practice_session_reports_users_user_id");
b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null)
.WithMany()
.HasForeignKey("TenantId", "BlueprintId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_practice_session_reports_practice_blueprints_tenant_id_blue~");
b.HasOne("Tiku.Domain.Content.QuestionCollection", null)
.WithMany()
.HasForeignKey("TenantId", "CollectionId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_practice_session_reports_question_collections_tenant_id_col~");
b.HasOne("Tiku.Domain.Learning.PracticeSession", null)
.WithMany()
.HasForeignKey("TenantId", "PracticeSessionId")

View File

@@ -43,7 +43,7 @@ public sealed class LearningEndpointTests
using var scope = factory.CreateSystemScope("Verify atomic V2 practice quota");
var db = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
Assert.Equal(1, await db.PracticeSessions.CountAsync(item =>
item.TenantId == seed.TenantId && item.UserId == seed.UserId && item.TargetId == releaseId));
item.TenantId == seed.TenantId && item.UserId == seed.UserId && item.ResourceId == releaseId));
Assert.Equal(1, await db.StudentEntitlementsV2
.Where(item => item.TenantId == seed.TenantId && item.UserId == seed.UserId)
.Select(item => item.UsedQuestionCount)