170 lines
6.9 KiB
C#
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"
|
|
}));
|
|
}
|
|
}
|