feat(ui/quiz): implement real-time global chapter quiz generation, submit results to database, and display dynamic statistics on dashboard
This commit is contained in:
@@ -11,4 +11,5 @@ public interface IIdentityService
|
||||
Task<Result> LogoutAsync();
|
||||
Task<Result<UserProfileDto>> GetProfileAsync();
|
||||
Task<Result> RefreshTokenAsync();
|
||||
void ClearCache();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using FluentResults;
|
||||
using NexusReader.Application.Abstractions.Messaging;
|
||||
|
||||
namespace NexusReader.Application.Commands.Quiz;
|
||||
|
||||
public record SubmitQuizResultCommand(
|
||||
string UserId,
|
||||
string Topic,
|
||||
int Score,
|
||||
int TotalQuestions) : ICommand;
|
||||
@@ -0,0 +1,44 @@
|
||||
using FluentResults;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NexusReader.Application.Abstractions.Messaging;
|
||||
using NexusReader.Data.Persistence;
|
||||
using NexusReader.Domain.Entities;
|
||||
|
||||
namespace NexusReader.Application.Commands.Quiz;
|
||||
|
||||
public sealed class SubmitQuizResultCommandHandler : ICommandHandler<SubmitQuizResultCommand>
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
|
||||
|
||||
public SubmitQuizResultCommandHandler(IDbContextFactory<AppDbContext> dbContextFactory)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<Result> Handle(SubmitQuizResultCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
return Result.Fail("User not found.");
|
||||
}
|
||||
|
||||
var quizResult = new QuizResult
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = request.UserId,
|
||||
TenantId = string.IsNullOrEmpty(user.TenantId) ? "global" : user.TenantId,
|
||||
Topic = request.Topic,
|
||||
Score = request.Score,
|
||||
TotalQuestions = request.TotalQuestions,
|
||||
CompletedDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
context.QuizResults.Add(quizResult);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result.Ok();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace NexusReader.Application.DTOs.User;
|
||||
public record UserProfileDto
|
||||
{
|
||||
public string Email { get; init; } = string.Empty;
|
||||
public string UserId { get; init; } = string.Empty;
|
||||
public int AITokensUsed { get; init; }
|
||||
public Guid TenantId { get; init; }
|
||||
|
||||
@@ -15,11 +16,11 @@ public record UserProfileDto
|
||||
|
||||
public int AverageQuizScore { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Summary of the last read book.
|
||||
/// </summary>
|
||||
public string? DisplayName { get; init; }
|
||||
public int BooksReadCount { get; init; }
|
||||
public int ConceptsMappedCount { get; init; }
|
||||
public LastReadBookDto? LastReadBook { get; init; }
|
||||
|
||||
public IReadOnlyList<QuizResultDto> RecentQuizzes { get; init; } = Array.Empty<QuizResultDto>();
|
||||
public string[] Roles { get; init; } = Array.Empty<string>();
|
||||
|
||||
// Helper properties for UI compatibility
|
||||
@@ -40,3 +41,13 @@ public record LastReadBookDto
|
||||
public string? Description { get; init; }
|
||||
public bool IsReadyForReading { get; init; }
|
||||
}
|
||||
|
||||
public record QuizResultDto
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public string Topic { get; init; } = string.Empty;
|
||||
public int Score { get; init; }
|
||||
public int TotalQuestions { get; init; }
|
||||
public double Percentage { get; init; }
|
||||
public DateTime CompletedDate { get; init; }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NexusReader.Application.Queries.Graph;
|
||||
@@ -7,7 +8,9 @@ public record GraphNodeDto(
|
||||
[property: JsonPropertyName("label")] string Label,
|
||||
[property: JsonPropertyName("group")] string Group,
|
||||
[property: JsonPropertyName("description")] string? Description = null,
|
||||
[property: JsonPropertyName("type")] string? Type = null
|
||||
[property: JsonPropertyName("type")] string? Type = null,
|
||||
[property: JsonPropertyName("summary")] string? Summary = null,
|
||||
[property: JsonPropertyName("key_terms")] List<string>? KeyTerms = null
|
||||
);
|
||||
|
||||
public record GraphLinkDto(
|
||||
|
||||
@@ -23,6 +23,7 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
|
||||
.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
|
||||
@@ -35,6 +36,9 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
|
||||
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),
|
||||
LastReadBook = u.Ebooks.OrderByDescending(e => e.LastReadDate).Select(e => new LastReadBookDto
|
||||
{
|
||||
Id = e.Id,
|
||||
@@ -51,6 +55,15 @@ 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(),
|
||||
Roles = dbContext.UserRoles
|
||||
.Where(ur => ur.UserId == u.Id)
|
||||
.Join(dbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r.Name!)
|
||||
|
||||
Reference in New Issue
Block a user