Add freemium access economy and token store
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QuestionsApi.Models;
|
||||
using QuestionsApi.Services;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace QuestionsApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/access")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public sealed class AccessController(
|
||||
AccessStore access,
|
||||
ExamSimulationStore simulations,
|
||||
RatingStore ratings,
|
||||
TimeProvider clock) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get() =>
|
||||
Ok(await access.GetStatusAsync(UserId(), User.IsInRole("SUPER")));
|
||||
|
||||
[HttpPost("redeem-credits")]
|
||||
public async Task<IActionResult> RedeemCredits(RedeemCreditsRequest request)
|
||||
{
|
||||
if (!ValidOperationId(request.OperationId)) return BadRequest("A valid operationId is required.");
|
||||
var status = await access.RedeemCreditsAsync(UserId(), request.OperationId.Trim());
|
||||
return status is null
|
||||
? Conflict(new { code = "insufficient_credits", message = "100 credits are required to redeem a day pass." })
|
||||
: Ok(status);
|
||||
}
|
||||
|
||||
[HttpPost("activate-token")]
|
||||
public async Task<IActionResult> ActivateToken(ActivateAccessTokenRequest request)
|
||||
{
|
||||
if (!ValidOperationId(request.OperationId)) return BadRequest("A valid operationId is required.");
|
||||
if (request.Token.Trim().ToLowerInvariant() is not ("day" or "week" or "month"))
|
||||
return BadRequest("token must be day, week, or month");
|
||||
var status = await access.ActivateTokenAsync(UserId(), request.OperationId.Trim(), request.Token);
|
||||
return status is null
|
||||
? Conflict(new { code = "token_unavailable", message = "That access token is not available." })
|
||||
: Ok(status);
|
||||
}
|
||||
|
||||
[HttpPost("qbank-reset")]
|
||||
public async Task<IActionResult> ResetQBank(ResetQBankRequest request)
|
||||
{
|
||||
if (request.Confirmation != "RESET_QBANK") return BadRequest("Type RESET_QBANK to confirm.");
|
||||
if (!ValidOperationId(request.OperationId)) return BadRequest("A valid operationId is required.");
|
||||
if (await simulations.FindActiveAsync(UserId()) is not null)
|
||||
return Conflict(new { code = "active_exam", message = "Finish or abandon the active exam simulation before resetting the QBank." });
|
||||
var cycle = await access.ApplyResetAsync(UserId(), request.OperationId.Trim());
|
||||
if (cycle is null)
|
||||
return Conflict(new { code = "reset_token_unavailable", message = "A QBank reset token is required." });
|
||||
await ratings.ResetUserAsync(UserId(), clock.GetUtcNow().UtcDateTime);
|
||||
return Ok(await access.GetStatusAsync(UserId(), User.IsInRole("SUPER")));
|
||||
}
|
||||
|
||||
[HttpPost("steam-checkouts")]
|
||||
public IActionResult SteamCheckout() => Problem(
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable,
|
||||
title: "Steam Wallet checkout is not configured",
|
||||
detail: "Steam purchases will be enabled after the Steamworks application and product catalog are configured.");
|
||||
|
||||
private string UserId() => User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue(ClaimTypes.Name)
|
||||
?? User.Identity?.Name
|
||||
?? throw new UnauthorizedAccessException();
|
||||
|
||||
private static bool ValidOperationId(string value) =>
|
||||
!string.IsNullOrWhiteSpace(value) && value.Trim().Length <= 100;
|
||||
}
|
||||
Reference in New Issue
Block a user