Add server-side ELO ratings
This commit is contained in:
@@ -13,11 +13,13 @@ public class QuestionsController : ControllerBase
|
||||
{
|
||||
private readonly QuestionStore _questions;
|
||||
private readonly QuestionAttemptStore _attempts;
|
||||
private readonly RatingStore _ratings;
|
||||
|
||||
public QuestionsController(QuestionStore questions, QuestionAttemptStore attempts)
|
||||
public QuestionsController(QuestionStore questions, QuestionAttemptStore attempts, RatingStore ratings)
|
||||
{
|
||||
_questions = questions;
|
||||
_attempts = attempts;
|
||||
_ratings = ratings;
|
||||
}
|
||||
|
||||
[HttpGet("systems")]
|
||||
@@ -34,10 +36,11 @@ public class QuestionsController : ControllerBase
|
||||
{
|
||||
var questions = await _questions.SearchAsync(req, User.IsInRole("SUPER"));
|
||||
var percentages = await _attempts.GetPercentagesByQuestionAsync(questions.Select(q => q.ExternalId));
|
||||
var questionElos = await _ratings.GetQuestionElosAsync(questions.Select(q => q.ExternalId));
|
||||
return Ok(questions.Select(q =>
|
||||
{
|
||||
percentages.TryGetValue(q.ExternalId, out var questionPercentages);
|
||||
return QuestionResponse.FromModel(q, questionPercentages);
|
||||
return QuestionResponse.FromModel(q, questionPercentages, questionElos.GetValueOrDefault(q.ExternalId, 1000));
|
||||
}).ToList());
|
||||
}
|
||||
|
||||
@@ -50,7 +53,8 @@ public class QuestionsController : ControllerBase
|
||||
return NotFound();
|
||||
|
||||
var percentages = await _attempts.GetPercentagesForQuestionAsync(question.ExternalId);
|
||||
return Ok(QuestionResponse.FromModel(question, percentages));
|
||||
var questionElo = await _ratings.GetQuestionEloAsync(question.ExternalId);
|
||||
return Ok(QuestionResponse.FromModel(question, percentages, questionElo));
|
||||
}
|
||||
|
||||
[HttpPost("attempts/first")]
|
||||
@@ -75,18 +79,60 @@ public class QuestionsController : ControllerBase
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
|
||||
await _attempts.RecordFirstAttemptAsync(
|
||||
var wasFirstAttempt = await _attempts.RecordFirstAttemptAsync(
|
||||
question.ExternalId,
|
||||
userId,
|
||||
req.ChoiceId.Trim(),
|
||||
req.ChoiceId.Trim() == question.CorrectChoiceId);
|
||||
var rating = await _ratings.ApplyFirstResultAsync(
|
||||
userId,
|
||||
question,
|
||||
req.ChoiceId.Trim() == question.CorrectChoiceId,
|
||||
wasFirstAttempt);
|
||||
|
||||
return Ok(new FirstQuestionAttemptResponse
|
||||
{
|
||||
QuestionExternalId = question.ExternalId,
|
||||
ChoiceSelectionPercentages = QuestionResponse.BuildChoiceSelectionPercentages(
|
||||
question,
|
||||
await _attempts.GetPercentagesForQuestionAsync(question.ExternalId))
|
||||
await _attempts.GetPercentagesForQuestionAsync(question.ExternalId)),
|
||||
QuestionElo = rating.QuestionElo,
|
||||
Rating = rating
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{externalId}/skip")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> Skip(string externalId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(externalId))
|
||||
return BadRequest("externalId required");
|
||||
|
||||
var question = await _questions.GetByExternalIdAsync(externalId.Trim());
|
||||
if (question is null || (!question.IsPublished && !User.IsInRole("SUPER")))
|
||||
return NotFound();
|
||||
|
||||
var userId = CurrentUserId();
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
|
||||
var wasFirstAttempt = await _attempts.RecordFirstAttemptAsync(
|
||||
question.ExternalId,
|
||||
userId,
|
||||
"",
|
||||
false,
|
||||
true);
|
||||
var rating = await _ratings.ApplyFirstResultAsync(userId, question, false, wasFirstAttempt);
|
||||
|
||||
return Ok(new SkipQuestionResponse
|
||||
{
|
||||
QuestionExternalId = question.ExternalId,
|
||||
CorrectChoiceId = question.CorrectChoiceId,
|
||||
ChoiceSelectionPercentages = QuestionResponse.BuildChoiceSelectionPercentages(
|
||||
question,
|
||||
await _attempts.GetPercentagesForQuestionAsync(question.ExternalId)),
|
||||
QuestionElo = rating.QuestionElo,
|
||||
Rating = rating
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,4 +229,10 @@ public class QuestionsController : ControllerBase
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string CurrentUserId() =>
|
||||
User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue(ClaimTypes.Name)
|
||||
?? User.Identity?.Name
|
||||
?? "";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QuestionsApi.Services;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace QuestionsApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class RatingsController : ControllerBase
|
||||
{
|
||||
private readonly RatingStore _ratings;
|
||||
|
||||
public RatingsController(RatingStore ratings)
|
||||
{
|
||||
_ratings = ratings;
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> Me()
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue(ClaimTypes.Name)
|
||||
?? User.Identity?.Name
|
||||
?? "";
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
|
||||
return Ok(await _ratings.GetProfileAsync(userId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user