93 lines
4.4 KiB
C#
93 lines
4.4 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using QuestionsApi.Services;
|
|
using QuestionsApi.Models;
|
|
using System.Security.Claims;
|
|
|
|
namespace QuestionsApi.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/credits")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public sealed class CreditsController(CreditStore credits, ExamSimulationStore simulations) : ControllerBase
|
|
{
|
|
[HttpGet]
|
|
public async Task<IActionResult> Get() => Ok(await credits.GetStatusAsync(UserId()));
|
|
|
|
[HttpGet("ledger")]
|
|
public async Task<IActionResult> Ledger([FromQuery] int limit = 20, [FromQuery] string? before = null)
|
|
{
|
|
DateTime? cursor = null;
|
|
if (!string.IsNullOrWhiteSpace(before))
|
|
{
|
|
if (!DateTime.TryParse(before, null, System.Globalization.DateTimeStyles.RoundtripKind, out var parsed))
|
|
return BadRequest(new { code = "invalid_cursor", message = "The ledger cursor is invalid." });
|
|
cursor = parsed;
|
|
}
|
|
return Ok(await credits.GetLedgerAsync(UserId(), limit, cursor));
|
|
}
|
|
|
|
[HttpPost("purchases")]
|
|
public async Task<IActionResult> Purchase([FromBody] CreditPurchaseRequest request)
|
|
{
|
|
var product = CreditEconomy.FindProduct(request.Sku);
|
|
if (product is null) return BadRequest(new { code = "unknown_sku", message = "Unknown store product." });
|
|
if (!ValidOperationId(request.OperationId))
|
|
return BadRequest(new { code = "invalid_operation_id", message = "A valid operation ID is required." });
|
|
var result = await credits.PurchaseAsync(UserId(), product, request.OperationId);
|
|
return result.Result switch
|
|
{
|
|
CreditOperationResult.Succeeded => Ok(result.Status),
|
|
CreditOperationResult.InsufficientCredits => Conflict(new
|
|
{ code = "insufficient_coins", message = "Not enough Rz Coins.", credits = result.Status }),
|
|
_ => Conflict(new
|
|
{ code = "operation_conflict", message = "That operation ID was already used.", credits = result.Status })
|
|
};
|
|
}
|
|
|
|
[HttpPost("qbank-reset")]
|
|
public async Task<IActionResult> ResetQBank([FromBody] QBankResetRequest request)
|
|
{
|
|
if (request.Confirmation != "RESET_QBANK")
|
|
return BadRequest(new { code = "confirmation_required", message = "Enter RESET_QBANK to confirm." });
|
|
if (!ValidOperationId(request.OperationId))
|
|
return BadRequest(new { code = "invalid_operation_id", message = "A valid operation ID is required." });
|
|
if (await simulations.FindActiveAsync(UserId()) is not null)
|
|
return Conflict(new { code = "active_exam", message = "Finish or end the active exam before resetting the QBank." });
|
|
var result = await credits.ApplyQBankResetAsync(UserId(), request.OperationId);
|
|
return result.Result == CreditOperationResult.Succeeded
|
|
? Ok(result.Status)
|
|
: Conflict(new
|
|
{ code = "reset_token_required", message = "A QBank Reset Token is required.", credits = result.Status });
|
|
}
|
|
|
|
[HttpGet("spin-config")]
|
|
public IActionResult SpinConfig() => Ok(new SpinConfigResponse());
|
|
|
|
[HttpPost("spin")]
|
|
public async Task<IActionResult> Spin([FromBody] SpinRequest request)
|
|
{
|
|
if (!ValidOperationId(request.OperationId))
|
|
return BadRequest(new { code = "invalid_operation_id", message = "A valid operation ID is required." });
|
|
var result = await credits.SpinAsync(UserId(), request.OperationId);
|
|
return result.Result switch
|
|
{
|
|
CreditOperationResult.Succeeded => Ok(result),
|
|
CreditOperationResult.InsufficientCredits => Conflict(new
|
|
{ code = "insufficient_coins", message = "Not enough Rz Coins.", credits = result.Status }),
|
|
CreditOperationResult.RateLimited => Conflict(new
|
|
{ code = "rate_limited", message = "Too many spins - try again later.", credits = result.Status }),
|
|
_ => Conflict(new
|
|
{ code = "operation_conflict", message = "That operation ID was already used.", credits = result.Status })
|
|
};
|
|
}
|
|
|
|
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.Length <= 100;
|
|
}
|