Files
xiong 33375a38d7
Some checks failed
ci / release-gate (push) Has been cancelled
refactor(architecture): harden module boundaries
2026-08-04 12:10:36 +08:00

112 lines
4.5 KiB
C#

using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Npgsql;
using Tiku.Application.Learning;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.Learning;
namespace Tiku.Infrastructure.Learning;
internal sealed class PracticeReportService(LearningServiceDependencies dependencies)
: LearningActivityServiceBase(dependencies), IPracticeReportService
{
public async Task<PracticeSessionReportItem> GetPracticeSessionReportAsync(
LearningActor actor,
PracticeSessionFilter filter,
CancellationToken cancellationToken = default)
{
if (!filter.PracticeSessionId.HasValue)
throw new LearningValidationException("practice_session_id_required", "Practice session id is required.");
var report = await learningPersistence.PracticeSessionReports
.AsNoTracking()
.SingleOrDefaultAsync(
item =>
item.TenantId == actor.TenantId &&
item.UserId == actor.UserId &&
item.PracticeSessionId == filter.PracticeSessionId.Value,
cancellationToken);
if (report is null)
throw new LearningResourceNotFoundException("practice_report_not_found",
"Practice session report was not found.");
return ToItem(report);
}
public async Task<LearningList<PracticeSessionReportItem>> GetPracticeReportsAsync(
LearningActor actor,
PracticeSessionFilter filter,
CancellationToken cancellationToken = default)
{
var query = learningPersistence.PracticeSessionReports
.AsNoTracking()
.Where(report =>
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
.OrderByDescending(report => report.SubmittedAt)
.Take(ResolveLimit(filter.Limit))
.ToArrayAsync(cancellationToken);
return new LearningList<PracticeSessionReportItem>(items.Select(ToItem).ToArray());
}
public async Task<LearningList<PracticeHistoryItem>> GetPracticeHistoryAsync(
LearningActor actor,
PracticeSessionFilter filter,
CancellationToken cancellationToken = default)
{
var query = learningPersistence.PracticeSessions
.AsNoTracking()
.Where(session =>
session.TenantId == actor.TenantId &&
session.UserId == actor.UserId);
if (!string.IsNullOrWhiteSpace(filter.Mode)) query = query.Where(session => session.Mode == filter.Mode.Trim());
var rows = await query
.GroupJoin(
learningPersistence.PracticeSessionReports.AsNoTracking(),
session => new { session.TenantId, PracticeSessionId = session.Id },
report => new { report.TenantId, report.PracticeSessionId },
(session, reports) => new { session, report = reports.FirstOrDefault() })
.OrderByDescending(row => row.session.FinishedAt ?? row.session.StartedAt)
.Take(ResolveLimit(filter.Limit))
.ToArrayAsync(cancellationToken);
var now = DateTimeOffset.UtcNow;
var items = rows
.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.QuestionCount,
row.report?.AnsweredCount ?? 0,
row.report?.CorrectCount ?? 0,
row.report?.WrongCount ?? 0,
row.session.StartedAt,
row.session.FinishedAt,
row.session.ExpiresAt,
ResolvePracticeSessionHistoryStatus(row.session, now),
row.report?.Id,
row.report?.Score,
row.report?.TotalScore,
row.report?.Accuracy))
.Where(item => string.IsNullOrWhiteSpace(filter.Status) || item.Status == filter.Status.Trim())
.ToArray();
return new LearningList<PracticeHistoryItem>(items);
}
}