feat(learning): harden access and practice sessions

This commit is contained in:
2026-08-05 14:01:36 +08:00
parent 603bc24c26
commit df31c3a669
88 changed files with 72411 additions and 543 deletions

View File

@@ -3,12 +3,15 @@ using Tiku.Application.Catalog;
using Tiku.Application.Content;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.Learning;
using Tiku.Domain.QuestionBanks;
using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.Content;
public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbContext) : IContentNavigationQueryService
public sealed class ContentNavigationQueryService(
IQuestionBankPersistence dbContext,
ILearningPersistence learningPersistence) : IContentNavigationQueryService
{
private const int DefaultLimit = 100;
private const int MaxLimit = 1000;
@@ -22,6 +25,9 @@ public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbCon
.Where(entry =>
entry.TenantId == filter.TenantId &&
entry.IsActive);
if (filter.AllowedRegionIds is not null)
query = query.Where(entry => entry.RegionId == null ||
filter.AllowedRegionIds.Contains(entry.RegionId.Value));
if (!filter.IncludeHidden) query = query.Where(entry => entry.Visibility != ContentVisibility.Hidden);
@@ -67,6 +73,9 @@ public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbCon
.Where(node =>
node.TenantId == filter.TenantId &&
node.EntryId == filter.EntryId.Value);
if (filter.AllowedRegionIds is not null)
query = query.Where(node => node.RegionId == null ||
filter.AllowedRegionIds.Contains(node.RegionId.Value));
if (!filter.IncludeInactive) query = query.Where(node => node.IsActive);
@@ -121,6 +130,13 @@ public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbCon
.Where(collection =>
collection.TenantId == filter.TenantId &&
collection.Status == ContentStatus.Active);
var allowedSliceIds = filter.AllowedContentSliceIds?.ToArray() ?? [];
query = query.Where(collection => learningPersistence.ContentSlices.Any(slice =>
slice.TenantId == filter.TenantId &&
allowedSliceIds.Contains(slice.Id) &&
slice.Status == ContentSliceStatus.Active &&
slice.ResourceType == LearningContentResourceType.Collection &&
slice.ResourceId == collection.Id));
if (filter.RegionId.HasValue)
query = query.Where(collection =>
@@ -173,6 +189,13 @@ public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbCon
.Where(blueprint =>
blueprint.TenantId == filter.TenantId &&
blueprint.Status == ContentStatus.Active);
var allowedSliceIds = filter.AllowedContentSliceIds?.ToArray() ?? [];
query = query.Where(blueprint => learningPersistence.ContentSlices.Any(slice =>
slice.TenantId == filter.TenantId &&
allowedSliceIds.Contains(slice.Id) &&
slice.Status == ContentSliceStatus.Active &&
slice.ResourceType == LearningContentResourceType.Blueprint &&
slice.ResourceId == blueprint.Id));
if (filter.RegionId.HasValue)
query = query.Where(blueprint => blueprint.RegionId == filter.RegionId.Value || blueprint.RegionId == null);
@@ -222,20 +245,25 @@ public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbCon
{
if (!filter.CollectionId.HasValue) throw new RequiredFieldException("collectionId is required.");
var allowedSliceIds = filter.AllowedContentSliceIds?.ToArray() ?? [];
var collectionExists = await dbContext.QuestionCollections
.AsNoTracking()
.AnyAsync(
collection =>
collection.TenantId == filter.TenantId &&
collection.Id == filter.CollectionId.Value &&
collection.Status == ContentStatus.Active,
collection.Status == ContentStatus.Active &&
learningPersistence.ContentSlices.Any(slice =>
slice.TenantId == filter.TenantId &&
allowedSliceIds.Contains(slice.Id) &&
slice.Status == ContentSliceStatus.Active &&
slice.ResourceType == LearningContentResourceType.Collection &&
slice.ResourceId == collection.Id),
cancellationToken);
if (!collectionExists) throw new ContentNavigationNotFoundException("Question collection was not found.");
var emptyOptions = JsonDefaults.Array();
var emptyCorrectOptionIndices = JsonDefaults.Array();
var emptySubQuestions = JsonDefaults.Array();
var query =
from item in dbContext.QuestionCollectionItems.AsNoTracking()
join question in dbContext.Questions.AsNoTracking()
@@ -271,11 +299,6 @@ public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbCon
version == null ? null : version.Id,
version == null ? null : version.Content,
version == null ? emptyOptions : version.Options,
version == null ? null : version.CorrectOptionIndex,
version == null ? emptyCorrectOptionIndices : version.CorrectOptionIndices,
version == null ? null : version.AnswerText,
version == null ? null : version.Explanation,
version == null ? emptySubQuestions : version.SubQuestions,
version == null ? null : version.CodeLang,
version == null ? null : version.CodeTemplate);
@@ -327,4 +350,4 @@ public sealed class ContentNavigationQueryService(IQuestionBankPersistence dbCon
: value.Replace("_", string.Empty, StringComparison.Ordinal)
.Replace("-", string.Empty, StringComparison.Ordinal);
}
}
}