fix: prevent potential component state updates after disposal and implement dedicated repository for quiz results

This commit is contained in:
2026-05-26 14:14:51 +02:00
parent 381f26ed3e
commit a2aecf7dd3
14 changed files with 323 additions and 132 deletions
@@ -18,14 +18,15 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
public async Task<Result<UserProfileDto>> Handle(GetUserProfileQuery request, CancellationToken cancellationToken)
{
using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var profile = await dbContext.Users
var userRaw = await dbContext.Users
.Where(u => u.Id == request.UserId)
.Select(u => new UserProfileDto
.Select(u => new
{
Email = u.Email ?? string.Empty,
UserId = u.Id,
AITokensUsed = u.AITokensUsed,
TenantId = u.TenantId != null && u.TenantId.Length == 36 ? new Guid(u.TenantId) : Guid.Empty,
TenantIdString = u.TenantId,
Plan = u.SubscriptionPlan != null ? new SubscriptionPlanDto
{
Id = u.SubscriptionPlan.Id,
@@ -33,12 +34,17 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
AITokenLimit = u.SubscriptionPlan.AITokenLimit,
MonthlyPrice = u.SubscriptionPlan.MonthlyPrice
} : new SubscriptionPlanDto(),
AverageQuizScore = u.QuizResults.Any(q => q.TotalQuestions > 0)
? (int)u.QuizResults.Where(q => q.TotalQuestions > 0).Average(q => (double)q.Score / q.TotalQuestions * 100)
: 0,
QuizResults = u.QuizResults.Select(q => new
{
q.Score,
q.TotalQuestions,
q.Id,
q.Topic,
q.Percentage,
q.CompletedDate
}).ToList(),
DisplayName = u.DisplayName,
BooksReadCount = u.Ebooks.Count(),
ConceptsMappedCount = dbContext.KnowledgeUnits.Count(k => k.TenantId == u.TenantId || k.TenantId == "global" || string.IsNullOrEmpty(k.TenantId)),
LastReadBook = u.Ebooks.OrderByDescending(e => e.LastReadDate).Select(e => new LastReadBookDto
{
Id = e.Id,
@@ -55,26 +61,6 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
Description = e.Description,
IsReadyForReading = e.IsReadyForReading
}).FirstOrDefault(),
RecentQuizzes = u.QuizResults.OrderByDescending(q => q.CompletedDate).Take(5).Select(q => new QuizResultDto
{
Id = q.Id,
Topic = q.Topic,
Score = q.Score,
TotalQuestions = q.TotalQuestions,
Percentage = q.Percentage,
CompletedDate = q.CompletedDate
}).ToList(),
MappedConcepts = dbContext.KnowledgeUnits
.Where(k => k.TenantId == u.TenantId || k.TenantId == "global" || string.IsNullOrEmpty(k.TenantId))
.OrderByDescending(k => k.CreatedAt)
.Take(6)
.Select(k => new MappedConceptDto
{
Id = k.Id,
Type = k.Type.ToString(),
Content = k.Content
})
.ToList(),
Roles = dbContext.UserRoles
.Where(ur => ur.UserId == u.Id)
.Join(dbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r.Name!)
@@ -82,11 +68,59 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
})
.FirstOrDefaultAsync(cancellationToken);
if (profile == null)
if (userRaw == null)
{
return Result.Fail("Profile not found.");
}
var tenantId = userRaw.TenantIdString;
var mappedConcepts = await dbContext.KnowledgeUnits
.Where(k => k.TenantId == tenantId || k.TenantId == "global" || string.IsNullOrEmpty(k.TenantId))
.OrderByDescending(k => k.CreatedAt)
.Take(6)
.Select(k => new MappedConceptDto
{
Id = k.Id,
Type = k.Type.ToString(),
Content = k.Content
})
.ToListAsync(cancellationToken);
var conceptsMappedCount = await dbContext.KnowledgeUnits
.CountAsync(k => k.TenantId == tenantId || k.TenantId == "global" || string.IsNullOrEmpty(k.TenantId), cancellationToken);
int averageQuizScore = 0;
var validQuizzes = userRaw.QuizResults.Where(q => q.TotalQuestions > 0).ToList();
if (validQuizzes.Count > 0)
{
averageQuizScore = (int)(validQuizzes.Average(q => (double)q.Score / q.TotalQuestions) * 100);
}
var profile = new UserProfileDto
{
Email = userRaw.Email,
UserId = userRaw.UserId,
AITokensUsed = userRaw.AITokensUsed,
TenantId = userRaw.TenantIdString != null && userRaw.TenantIdString.Length == 36 ? new Guid(userRaw.TenantIdString) : Guid.Empty,
Plan = userRaw.Plan,
AverageQuizScore = averageQuizScore,
DisplayName = userRaw.DisplayName,
BooksReadCount = userRaw.BooksReadCount,
ConceptsMappedCount = conceptsMappedCount,
LastReadBook = userRaw.LastReadBook,
RecentQuizzes = userRaw.QuizResults.OrderByDescending(q => q.CompletedDate).Take(5).Select(q => new QuizResultDto
{
Id = q.Id,
Topic = q.Topic,
Score = q.Score,
TotalQuestions = q.TotalQuestions,
Percentage = q.Percentage,
CompletedDate = q.CompletedDate
}).ToList(),
MappedConcepts = mappedConcepts,
Roles = userRaw.Roles
};
return Result.Ok(profile);
}
}