Files
Nexus.Reader/src/NexusReader.Application/Queries/User/GetUserProfileQueryHandler.cs
T
Antigravity aa80c2ba3e feat(ui/quiz): implement real-time global chapter quiz generation, submit results to database, and display dynamic statistics on dashboard (#53)
This PR fully implements the Global Chapter-Level Quiz Generation system in the NexusReader application.

### Key Accomplishments:
1. **SubmitQuizResultCommand**: Added MediatR command and handler to persist completed quiz results to the SQLite database securely, using our clean architecture result-pattern.
2. **Dynamic Dashboard Integration**: Re-engineered the user dashboard to fetch, calculate, and display real-time statistics (average score, total books read, total concept nodes mapped, and list of resolved quizzes with their dates and scores) directly from active database queries, eliminating static mockups.
3. **Haptic & Visual Feedback**: Enhanced the quiz flow with interactive CSS transitions, glowing hover feedback, and clear result visualization upon completion.
4. **Robust Verification**: Implemented comprehensive unit tests for `SubmitQuizResultCommandHandler` covering all success and failure/edge cases. Executed full `dotnet test` with 100% success rate.

---------

Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Reviewed-on: #53
Co-authored-by: Antigravity <antigravity@google.com>
Co-committed-by: Antigravity <antigravity@google.com>
2026-05-26 11:43:58 +00:00

93 lines
4.0 KiB
C#

using MediatR;
using FluentResults;
using Microsoft.EntityFrameworkCore;
using NexusReader.Application.DTOs.User;
using NexusReader.Data.Persistence;
namespace NexusReader.Application.Queries.User;
public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, Result<UserProfileDto>>
{
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
public GetUserProfileQueryHandler(IDbContextFactory<AppDbContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public async Task<Result<UserProfileDto>> Handle(GetUserProfileQuery request, CancellationToken cancellationToken)
{
using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var profile = await dbContext.Users
.Where(u => u.Id == request.UserId)
.Select(u => new UserProfileDto
{
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,
Plan = u.SubscriptionPlan != null ? new SubscriptionPlanDto
{
Id = u.SubscriptionPlan.Id,
Name = u.SubscriptionPlan.PlanName,
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,
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,
Title = e.Title,
Author = new AuthorDto
{
Id = e.Author.Id,
Name = e.Author.Name
},
CoverUrl = e.CoverUrl,
Progress = e.Progress,
LastChapter = e.LastChapter ?? "Rozpoczynanie...",
LastChapterIndex = e.LastChapterIndex,
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!)
.ToArray()
})
.FirstOrDefaultAsync(cancellationToken);
if (profile == null)
{
return Result.Fail("Profile not found.");
}
return Result.Ok(profile);
}
}