91 lines
4.3 KiB
C#
91 lines
4.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Application.Content;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.Infrastructure.ContentV2;
|
|
|
|
internal sealed class ContentCandidateReader(ITenantExecutionScope tenantExecutionScope) : IContentCandidateReader
|
|
{
|
|
public async Task<IReadOnlyCollection<ContentCandidateItem>> GetCandidatesAsync(
|
|
ContentCandidateQuery query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (query.ContentReleaseIds.Count == 0 || query.AudienceSegmentIds.Count == 0) return [];
|
|
var limit = Math.Clamp(query.Limit, 1, 500);
|
|
return await tenantExecutionScope.ExecuteAsync(
|
|
new SystemScopeRequest(
|
|
query.ContentOwnerTenantId,
|
|
SystemScopeCallerType.PublicQuestionBank,
|
|
nameof(ContentCandidateReader),
|
|
"Read compiled content candidates with an explicit content owner",
|
|
Guid.NewGuid().ToString("N")),
|
|
async (provider, token) =>
|
|
{
|
|
var persistence = provider.GetRequiredService<IQuestionBankPersistence>();
|
|
var releaseIds = query.ContentReleaseIds.ToArray();
|
|
var segmentIds = query.AudienceSegmentIds.ToArray();
|
|
var candidates = persistence.ContentReleaseQuestions.AsNoTracking().Where(item =>
|
|
item.TenantId == query.ContentOwnerTenantId &&
|
|
releaseIds.Contains(item.ContentReleaseId) &&
|
|
segmentIds.Contains(item.AudienceSegmentId) &&
|
|
item.Status == ContentReleaseQuestionStatus.Active);
|
|
if (query.CurriculumNodeId.HasValue)
|
|
candidates = candidates.Where(item => item.CurriculumNodeId == query.CurriculumNodeId.Value);
|
|
if (!string.IsNullOrWhiteSpace(query.QuestionType))
|
|
{
|
|
var type = query.QuestionType.Trim();
|
|
candidates = candidates.Where(item => item.QuestionType == type);
|
|
}
|
|
if (query.Difficulty.HasValue)
|
|
candidates = candidates.Where(item => item.Difficulty == query.Difficulty.Value);
|
|
if (query.CollectionReleaseIds?.Count > 0)
|
|
{
|
|
var collectionIds = query.CollectionReleaseIds.ToArray();
|
|
candidates = candidates.Where(candidate => persistence.CollectionReleaseQuestions.Any(item =>
|
|
item.TenantId == query.ContentOwnerTenantId &&
|
|
collectionIds.Contains(item.CollectionReleaseId) &&
|
|
item.ContentReleaseQuestionId == candidate.Id));
|
|
}
|
|
|
|
var count = await candidates.CountAsync(token);
|
|
if (count == 0) return Array.Empty<ContentCandidateItem>();
|
|
var start = (int)((uint)query.Seed % (uint)count);
|
|
var ordered = candidates
|
|
.OrderBy(item => item.ContentReleaseId)
|
|
.ThenBy(item => item.AudienceSegmentId)
|
|
.ThenBy(item => item.CurriculumNodeId)
|
|
.ThenBy(item => item.Ordinal)
|
|
.ThenBy(item => item.Id);
|
|
var first = await Project(ordered.Skip(start).Take(limit))
|
|
.ToArrayAsync(token);
|
|
if (first.Length == limit || start == 0) return first;
|
|
var wrapped = await Project(ordered.Take(limit - first.Length))
|
|
.ToArrayAsync(token);
|
|
return first.Concat(wrapped).DistinctBy(item => item.ContentReleaseQuestionId).ToArray();
|
|
},
|
|
cancellationToken);
|
|
}
|
|
|
|
private static IQueryable<ContentCandidateItem> Project(IQueryable<ContentReleaseQuestion> query)
|
|
{
|
|
return query.Select(item => new ContentCandidateItem(
|
|
item.Id,
|
|
item.TenantId,
|
|
item.ContentReleaseId,
|
|
item.AudienceSegmentId,
|
|
item.CurriculumNodeId,
|
|
item.QuestionPlacementId,
|
|
item.QuestionAssetOwnerTenantId,
|
|
item.QuestionAssetId,
|
|
item.QuestionRevisionId,
|
|
item.AssessmentPolicyVersionId,
|
|
item.Ordinal,
|
|
item.Difficulty,
|
|
item.QuestionType));
|
|
}
|
|
}
|