Files
tiku-backend.net/Tiku.Infrastructure/Learning/QuestionReview/QuestionReviewService.cs

170 lines
6.9 KiB
C#

using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Learning;
using Tiku.Domain.Learning;
namespace Tiku.Infrastructure.Learning;
internal sealed class QuestionReviewService(LearningServiceDependencies dependencies)
: LearningActivityServiceBase(dependencies), IQuestionReviewService
{
public async Task<LearningList<FavoriteQuestionItem>> GetFavoriteQuestionsAsync(
LearningActor actor,
LearningLimitFilter filter,
CancellationToken cancellationToken = default)
{
var items = await learningPersistence.FavoriteQuestions
.AsNoTracking()
.Where(item =>
item.TenantId == actor.TenantId &&
item.UserId == actor.UserId)
.OrderByDescending(item => item.CreatedAt)
.Take(ResolveLimit(filter.Limit))
.Select(item => new FavoriteQuestionItem(
item.QuestionAssetOwnerTenantId,
item.QuestionAssetId,
item.LastQuestionRevisionId,
item.LastQuestionPlacementId,
item.CreatedAt))
.ToArrayAsync(cancellationToken);
return new LearningList<FavoriteQuestionItem>(items);
}
public async Task<LearningActionResult> ToggleFavoriteQuestionAsync(
LearningActor actor,
QuestionActionCommand command,
CancellationToken cancellationToken = default)
{
var delivery = await (
from sessionQuestion in learningPersistence.PracticeSessionQuestions.AsNoTracking()
join session in learningPersistence.PracticeSessions.AsNoTracking()
on new { sessionQuestion.TenantId, Id = sessionQuestion.PracticeSessionId }
equals new { session.TenantId, session.Id }
where sessionQuestion.TenantId == actor.TenantId &&
session.UserId == actor.UserId &&
sessionQuestion.Id == command.SessionQuestionId
select new
{
QuestionAssetOwnerTenantId = sessionQuestion.QuestionOwnerTenantId,
sessionQuestion.QuestionAssetId,
sessionQuestion.QuestionRevisionId,
sessionQuestion.QuestionPlacementId
})
.SingleOrDefaultAsync(cancellationToken);
if (delivery is null)
throw new LearningResourceNotFoundException(
"practice_question_not_found", "The delivered practice question was not found.");
var favorite = command.Favorite ?? true;
var item = await learningPersistence.FavoriteQuestions.FindAsync(
[actor.TenantId, actor.UserId, delivery.QuestionAssetOwnerTenantId, delivery.QuestionAssetId],
cancellationToken);
if (favorite)
{
if (item is null)
learningPersistence.FavoriteQuestions.Add(new FavoriteQuestion
{
TenantId = actor.TenantId,
UserId = actor.UserId,
QuestionAssetOwnerTenantId = delivery.QuestionAssetOwnerTenantId,
QuestionAssetId = delivery.QuestionAssetId,
LastQuestionRevisionId = delivery.QuestionRevisionId,
LastQuestionPlacementId = delivery.QuestionPlacementId,
CreatedAt = DateTimeOffset.UtcNow
});
else
{
item.LastQuestionRevisionId = delivery.QuestionRevisionId;
item.LastQuestionPlacementId = delivery.QuestionPlacementId;
}
}
else if (item is not null)
{
learningPersistence.FavoriteQuestions.Remove(item);
}
await unitOfWork.SaveChangesAsync(cancellationToken);
return new LearningActionResult(true, favorite);
}
public async Task<LearningList<WrongQuestionItem>> GetWrongQuestionsAsync(
LearningActor actor,
LearningLimitFilter filter,
CancellationToken cancellationToken = default)
{
var query = learningPersistence.WrongQuestions
.AsNoTracking()
.Where(item =>
item.TenantId == actor.TenantId &&
item.UserId == actor.UserId);
if (!string.Equals(filter.Status, "all", StringComparison.OrdinalIgnoreCase))
query = query.Where(item => item.ResolvedAt == null);
var items = await query
.OrderByDescending(item => item.LastWrongAt)
.Take(ResolveLimit(filter.Limit))
.Select(item => new WrongQuestionItem(
item.QuestionAssetOwnerTenantId,
item.QuestionAssetId,
item.LastQuestionRevisionId,
item.LastQuestionPlacementId,
item.WrongCount,
item.LastWrongAt,
item.ResolvedAt))
.ToArrayAsync(cancellationToken);
return new LearningList<WrongQuestionItem>(items);
}
public async Task<LearningActionResult> ResolveWrongQuestionAsync(
LearningActor actor,
ResolveWrongQuestionCommand command,
CancellationToken cancellationToken = default)
{
var item = await learningPersistence.WrongQuestions.FindAsync(
[actor.TenantId, actor.UserId, command.QuestionAssetOwnerTenantId, command.QuestionAssetId],
cancellationToken);
if (item is null)
throw new LearningResourceNotFoundException("wrong_question_not_found", "Wrong question was not found.");
item.ResolvedAt = DateTimeOffset.UtcNow;
await unitOfWork.SaveChangesAsync(cancellationToken);
return new LearningActionResult(true);
}
public async Task<WrongQuestionReviewPlan> GetWrongQuestionReviewPlanAsync(
LearningActor actor,
LearningLimitFilter filter,
CancellationToken cancellationToken = default)
{
var items = await learningPersistence.WrongQuestions.AsNoTracking()
.Where(item =>
item.TenantId == actor.TenantId &&
item.UserId == actor.UserId &&
item.ResolvedAt == null)
.OrderByDescending(item => item.WrongCount)
.ThenBy(item => item.LastWrongAt)
.Take(ResolveLimit(filter.Limit))
.Select(item => new WrongQuestionReviewPlanItem(
item.QuestionAssetOwnerTenantId,
item.QuestionAssetId,
item.LastQuestionRevisionId,
item.LastQuestionPlacementId,
item.WrongCount,
item.LastWrongAt))
.ToArrayAsync(cancellationToken);
return new WrongQuestionReviewPlan(
items,
JsonSerializer.SerializeToElement(new
{
mode = "wrong_review",
questionCount = items.Length,
recommendedEndpoint = "/api/student/learning/practice-sessions"
}));
}
}