Add freemium access economy and token store

This commit is contained in:
hz
2026-07-27 22:20:21 -05:00
parent 1d5d03a4f6
commit 4bbac27b84
40 changed files with 1807 additions and 37 deletions
@@ -9,7 +9,7 @@ namespace QuestionsApi.Controllers;
[ApiController]
[Route("api/exam-simulations")]
[Authorize(Roles = "USER,SUPER")]
public class ExamSimulationsController(ExamSimulationStore simulations) : ControllerBase
public class ExamSimulationsController(ExamSimulationStore simulations, AccessStore access) : ControllerBase
{
[HttpGet("readiness")]
public async Task<IActionResult> Readiness() => Ok(await simulations.GetReadinessAsync(UserId()));
@@ -17,12 +17,28 @@ public class ExamSimulationsController(ExamSimulationStore simulations) : Contro
[HttpPost]
public async Task<IActionResult> Create()
{
var operationId = $"exam-create:{Guid.NewGuid():N}";
if (!User.IsInRole("SUPER") && !await access.TryConsumeExamTokenAsync(UserId(), operationId))
return StatusCode(StatusCodes.Status402PaymentRequired, new
{
code = "exam_token_required",
message = "An exam simulation token is required."
});
try
{
var simulation = await simulations.CreateAsync(UserId());
return Ok(await simulations.ToResponseAsync(simulation));
}
catch (InvalidOperationException exception) { return Conflict(exception.Message); }
catch (InvalidOperationException exception)
{
if (!User.IsInRole("SUPER")) await access.RefundExamTokenAsync(UserId(), operationId);
return Conflict(exception.Message);
}
catch
{
if (!User.IsInRole("SUPER")) await access.RefundExamTokenAsync(UserId(), operationId);
throw;
}
}
[HttpGet("preview-results")]