72 lines
3.3 KiB
C#
72 lines
3.3 KiB
C#
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;
|
|
}
|