diff --git a/WowsKarma.Api/Controllers/AuthController.cs b/WowsKarma.Api/Controllers/AuthController.cs
index 9bc28a8..e0dc35a 100644
--- a/WowsKarma.Api/Controllers/AuthController.cs
+++ b/WowsKarma.Api/Controllers/AuthController.cs
@@ -35,8 +35,8 @@ public AuthController(IConfiguration config, UserService userService, WargamingA
///
/// Authentication successful.
/// Authentication failed.
- [HttpHead, Authorize, ProducesResponseType(200), ProducesResponseType(401)]
- public IActionResult ValidateAuth() => StatusCode(200);
+ [HttpHead, Authorize]
+ public ActionResult ValidateAuth() => Ok();
///
/// Provides redirection to Wargaming OpenID Authentication.
@@ -52,13 +52,13 @@ public AuthController(IConfiguration config, UserService userService, WargamingA
/// Authentication successful.
/// Invalid callback request.
[HttpGet("wg-callback"), ProducesResponseType(302), ProducesResponseType(200), ProducesResponseType(403)]
- public async Task WgAuthCallback()
+ public async Task WgAuthCallbackAsync()
{
bool valid = await _wargamingAuthService.VerifyIdentity(Request);
if (!valid)
{
- return StatusCode(403);
+ return Forbid();
}
JwtSecurityToken token = await _userService.CreateTokenAsync(WargamingIdentity.FromUri(new(Request.Query["openid.identity"].FirstOrDefault()
@@ -89,10 +89,9 @@ public async Task WgAuthCallback()
/// Seed Token successfully reset.
/// Authentication failed.
[HttpPost("renew-seed"), Authorize, ProducesResponseType(200), ProducesResponseType(401)]
- public async Task RenewSeed()
+ public async Task RenewSeed()
{
await _userService.RenewSeedTokenAsync(uint.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? throw new BadHttpRequestException("Missing NameIdentifier claim.")));
- return Ok();
}
///
@@ -101,9 +100,9 @@ public async Task RenewSeed()
/// Token successfully refreshed.
/// Authentication failed.
[HttpGet("refresh-token"), Authorize, ProducesResponseType(typeof(string), 200), ProducesResponseType(401)]
- public async Task RefreshToken()
+ public async Task RefreshToken()
{
JwtSecurityToken token = await _userService.CreateTokenAsync(new(User.Claims));
- return StatusCode(200, _jwtService.TokenHandler.WriteToken(token));
+ return _jwtService.TokenHandler.WriteToken(token);
}
}
diff --git a/WowsKarma.Api/Controllers/PlayerController.cs b/WowsKarma.Api/Controllers/PlayerController.cs
index 9a57cd9..d9ffb43 100644
--- a/WowsKarma.Api/Controllers/PlayerController.cs
+++ b/WowsKarma.Api/Controllers/PlayerController.cs
@@ -1,3 +1,4 @@
+using System.Collections;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
@@ -23,7 +24,7 @@ public PlayerController(PlayerService playerService)
///
/// A list of all players in the database.
/// Returns all players in the database.
- [HttpGet, ProducesResponseType(typeof(IEnumerable), 200)]
+ [HttpGet]
public IAsyncEnumerable ListPlayers() => _playerService.ListPlayerIds();
///
@@ -34,7 +35,7 @@ public PlayerController(PlayerService playerService)
/// Account listings for given search query
/// No results found for given search query
[HttpGet("search/{query}"), ProducesResponseType(typeof(IEnumerable), 200), ProducesResponseType(204)]
- public async Task SearchAccount([StringLength(100, MinimumLength = 3), RegularExpression(@"^[a-zA-Z0-9_]*$")] string query)
+ public async Task> SearchAccount([StringLength(100, MinimumLength = 3), RegularExpression(@"^[a-zA-Z0-9_]*$")] string query)
=> await _playerService.ListPlayersAsync(query) is { Length: not 0 } accounts
? Ok(accounts)
: NoContent();
@@ -46,12 +47,13 @@ public async Task SearchAccount([StringLength(100, MinimumLength
/// Include clan membership info while fetching player profile.
/// Returns player profile
/// No profile found
- [HttpGet("{id}"), ProducesResponseType(typeof(PlayerProfileDTO), 200), ProducesResponseType(204)]
- public async Task GetAccount(uint id, bool includeClanInfo = true)
+ [HttpGet("{id}")]
+ public async Task> GetAccount(uint id, bool includeClanInfo = true)
{
if (id is 0)
{
- return BadRequest(new ArgumentException(null, nameof(id)));
+ ModelState.AddModelError(nameof(id), "Account ID cannot be zero.");
+ return BadRequest(ModelState);
}
Player? playerProfile = await _playerService.GetPlayerAsync(id, false, includeClanInfo);
@@ -67,15 +69,15 @@ public async Task GetAccount(uint id, bool includeClanInfo = true
/// List of Account IDs
/// Returns "Account":"SiteKarma" Dictionary of Karma metrics for available accounts (may be empty).
[HttpPost("karmas"), ProducesResponseType(typeof(Dictionary), 200)]
- public IActionResult FetchKarmas([FromBody] uint[] ids) => Ok(AccountKarmaDTO.ToDictionary(_playerService.GetPlayersKarma(ids)));
+ public Dictionary FetchKarmas([FromBody] uint[] ids) => AccountKarmaDTO.ToDictionary(_playerService.GetPlayersKarma(ids));
///
/// Fetches full Karma metrics (Site Karma and Flairs) for each provided Account ID, where available.
///
/// List of Account IDs
/// Returns Full Karma metrics for available accounts (may be empty).
- [HttpPost("karmas-full"), ProducesResponseType(typeof(IEnumerable), 200)]
- public IActionResult FetchFullKarmas([FromBody] uint[] ids) => Ok(_playerService.GetPlayersFullKarma(ids));
+ [HttpPost("karmas-full")]
+ public IEnumerable FetchFullKarmas([FromBody] uint[] ids) => _playerService.GetPlayersFullKarma(ids);
///
/// Triggers recalculation of Karma metrics for a given account.
@@ -86,8 +88,10 @@ public async Task GetAccount(uint id, bool includeClanInfo = true
/// Account ID of player profile
///
/// Profile Karma recalculation was processed.
- [HttpPatch("recalculate"), Authorize(Roles = ApiRoles.Administrator), ProducesResponseType(205), ProducesResponseType(401), ProducesResponseType(403)]
- public IActionResult RecalculateMetrics([FromQuery] uint playerId, CancellationToken ct)
+ /// Unauthorized
+ /// Forbidden
+ [HttpPatch("recalculate"), Authorize(Roles = ApiRoles.Administrator)]
+ public AcceptedResult RecalculateMetrics([FromQuery] uint playerId, CancellationToken ct)
{
BackgroundJob.Enqueue(p => p.RecalculatePlayerMetrics(playerId, ct));
return Accepted();
diff --git a/WowsKarma.Api/Controllers/PostController.cs b/WowsKarma.Api/Controllers/PostController.cs
index 7f63cfa..5d660c0 100644
--- a/WowsKarma.Api/Controllers/PostController.cs
+++ b/WowsKarma.Api/Controllers/PostController.cs
@@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using System.Text.Json;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.EntityFrameworkCore;
using WowsKarma.Api.Infrastructure.Attributes;
using WowsKarma.Api.Infrastructure.Data;
@@ -44,8 +45,8 @@ public PostController(PlayerService playerService, PostService postService, ILog
/// Returns object of Post with specified ID
/// No post was found with given ID.
/// Post is locked by Community Managers.
- [HttpGet("{postId:guid}"), ProducesResponseType(typeof(PlayerPostDTO), 200), ProducesResponseType(404), ProducesResponseType(410)]
- public async Task GetPostAsync(Guid postId)
+ [HttpGet("{postId:guid}")]
+ public async Task> GetPostAsync(Guid postId)
=> await _postService.GetPostDTOAsync(postId) is { } post
? !post.ModLocked || post.Author.Id == User.ToAccountListing()?.Id || User.IsInRole(ApiRoles.CM)
? Ok(post)
@@ -60,8 +61,8 @@ public async Task GetPostAsync(Guid postId)
/// Number of posts per page
/// List of posts received by player.
/// No player found for given Account ID.
- [HttpGet("{userId}/received"), ProducesResponseType(typeof(IEnumerable), 200)]
- public IActionResult GetReceivedPosts(
+ [HttpGet("{userId}/received")]
+ public ActionResult GetReceivedPosts(
[FromRoute] uint userId,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10
@@ -89,7 +90,7 @@ public IActionResult GetReceivedPosts(
}
}
- return Ok(pageResults.Items.Adapt>());
+ return Ok(pageResults.Items.Adapt());
}
///
@@ -101,8 +102,8 @@ public IActionResult GetReceivedPosts(
/// List of posts sent by player
/// No posts sent by given player.
/// No player found for given Account ID.
- [HttpGet("{userId}/sent"), ProducesResponseType(typeof(IEnumerable), 200), ProducesResponseType(204), ProducesResponseType(typeof(string), 404)]
- public IActionResult GetSentPosts(
+ [HttpGet("{userId}/sent")]
+ public ActionResult GetSentPosts(
[FromRoute] uint userId,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10
@@ -129,7 +130,7 @@ public IActionResult GetSentPosts(
}
}
- return Ok(pageResults.Items.Adapt>());
+ return Ok(pageResults.Items.Adapt());
}
///
@@ -140,8 +141,8 @@ public IActionResult GetSentPosts(
/// Filters returned posts by Replay attachment.
/// Hides posts containing Mod Actions (visible only to CMs).
/// List of latest posts, sorted by Submission time.
- [HttpGet("latest"), ProducesResponseType(typeof(IEnumerable), 200)]
- public IActionResult GetLatestPosts(
+ [HttpGet("latest")]
+ public ActionResult GetLatestPosts(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10,
[FromQuery] bool? hasReplay = null,
@@ -182,7 +183,7 @@ public IActionResult GetLatestPosts(
}
}
- return Ok(pageResults.Items.Adapt>());
+ return Ok(pageResults.Items.Adapt());
}
///
@@ -192,18 +193,16 @@ public IActionResult GetLatestPosts(
/// Optional replay file to attach to post
/// Bypass API Validation for post creation (Admin only)
/// Post was successfuly created.
- /// Post contents validation has failed.
+ /// Post validation has failed.
/// Attached replay is invalid.
- /// Restrictions are in effect for one of the targeted accounts.
/// One of the targeted accounts was not found.
[HttpPost, Authorize(RequireNoPlatformBans), UserAtomicLock]
- [ProducesResponseType(201), ProducesResponseType(400), ProducesResponseType(422), ProducesResponseType(typeof(string), 403), ProducesResponseType(typeof(string), 404)]
- public async Task CreatePost(
+ public async Task> CreatePostAsync(
[FromForm] string postDto,
[FromServices] ReplaysIngestService replaysIngestService,
IFormFile? replay = null,
- [FromQuery] bool ignoreChecks = false)
- {
+ [FromQuery] bool ignoreChecks = false
+ ) {
PlayerPostDTO post;
try
@@ -212,52 +211,60 @@ public async Task CreatePost(
}
catch (Exception e)
{
- return BadRequest(e.ToString());
+ ModelState.TryAddModelException(nameof(postDto), e);
+ return BadRequest(ModelState);
}
if (await _playerService.GetPlayerAsync(post.Author.Id) is not { } author)
{
- return StatusCode(404, $"Account {post.Author.Id} not found.");
+ ModelState.AddModelError(nameof(post.Author.Id), $"Account {post.Author.Id} not found.");
+ return BadRequest(ModelState);
}
if (await _playerService.GetPlayerAsync(post.Player.Id) is not { } player)
{
- return StatusCode(404, $"Account {post.Player.Id} not found.");
+ ModelState.AddModelError(nameof(post.Player.Id), $"Account {post.Player.Id} not found.");
+ return BadRequest(ModelState);
}
if (ignoreChecks)
{
if (!(User.IsInRole(ApiRoles.CM) || User.IsInRole(ApiRoles.Administrator)))
{
- return StatusCode(403, "Post Author is not authorized to bypass Post checks.");
+ ModelState.AddModelError(nameof(ignoreChecks), "Post Author is not authorized to bypass Post checks.");
+ return BadRequest(ModelState);
}
}
else
{
if (post.Author.Id != uint.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? throw new BadHttpRequestException("Missing NameIdentifier claim.")))
{
- return StatusCode(403, "Author is not authorized to post on behalf of other users.");
+ ModelState.AddModelError(nameof(post.Author.Id), "Author is not authorized to post on behalf of other users.");
+ return BadRequest(ModelState);
}
if (author.OptedOut)
{
- return StatusCode(403, "Post Author has opted-out from using this platform.");
+ ModelState.AddModelError(nameof(post.Author.Id), "Post Author has opted-out from using this platform.");
+ return BadRequest(ModelState);
}
if (player.OptedOut)
{
- return StatusCode(403, "Targeted player has opted-out from using this platform.");
+ ModelState.AddModelError(nameof(post.Player.Id), "Targeted player has opted-out from using this platform.");
+ return BadRequest(ModelState);
}
}
try
{
using Post created = await _postService.CreatePostAsync(post, replay, ignoreChecks);
- return StatusCode(201, created.Id);
+ return CreatedAtAction("GetPost", new { postId = created.Id }, created.Adapt());
}
- catch (ArgumentException)
+ catch (ArgumentException e)
{
- return BadRequest();
+ ModelState.TryAddModelException(nameof(post), e);
+ return BadRequest(ModelState);
}
catch (InvalidReplayException e) when (e.InnerException is Nodsoft.WowsReplaysUnpack.Core.Exceptions.InvalidReplayException)
@@ -267,8 +274,8 @@ public async Task CreatePost(
// Handle InvalidReplayException when the Inner exception is a SecurityException and its Data contains an exploit with value "CVE-2022-31265".
catch (InvalidReplayException e) when (e.InnerException is SecurityException se && se.Data["exploit"] is "CVE-2022-31265")
{
- // Log this exception, and store the replay with the RCE the samples.
- _logger.LogWarning(se, "Replay upload failed for post author {author} due to CVE-2022-31265 exploit detection.", post.Author.Id);
+ // Log this exception, and store the replay with the RCE samples.
+ _logger.LogWarning(se, "Replay upload failed for post author {Author} due to CVE-2022-31265 exploit detection", post.Author.Id);
await replaysIngestService.IngestRceFileAsync(replay!);
throw se;
@@ -285,31 +292,34 @@ public async Task CreatePost(
/// Restrictions are in effect for the existing post.
/// Targeted post was not found.
[HttpPut, Authorize(RequireNoPlatformBans), ETag(false)]
- [ProducesResponseType(200), ProducesResponseType(400), ProducesResponseType(typeof(string), 403), ProducesResponseType(typeof(string), 404)]
- public async Task EditPost([FromBody] PlayerPostDTO post, [FromQuery] bool ignoreChecks = false)
+ public async Task EditPost([FromBody] PlayerPostDTO post, [FromQuery] bool ignoreChecks = false)
{
if (_postService.GetPost(post.Id ?? Guid.Empty) is not { } current)
{
- return StatusCode(404, $"No post with ID {post.Id} found.");
+ ModelState.AddModelError(nameof(post.Id), $"No post with ID {post.Id} found.");
+ return NotFound(ModelState);
}
if (ignoreChecks)
{
if (!(User.IsInRole(ApiRoles.CM) || User.IsInRole(ApiRoles.Administrator)))
{
- return StatusCode(403, "Post Author is not authorized to bypass Post checks.");
+ ModelState.AddModelError(nameof(ignoreChecks), "Post Author is not authorized to bypass Post checks.");
+ return BadRequest(ModelState);
}
}
else
{
if (current.AuthorId != uint.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? throw new BadHttpRequestException("Missing NameIdentifier claim.")))
{
- return StatusCode(403, "Author is not authorized to edit posts on behalf of other users.");
+ ModelState.AddModelError(nameof(post.Id), "Author is not authorized to edit posts on behalf of other users.");
+ return BadRequest(ModelState);
}
if (current is { ModLocked: true } or { ReadOnly: true })
{
- return StatusCode(403, "Post has been locked by Community Managers. No modification is possible.");
+ ModelState.AddModelError(nameof(post.Id), "Post has been locked by Community Managers. No modification is possible.");
+ return BadRequest(ModelState);
}
}
@@ -320,7 +330,8 @@ public async Task EditPost([FromBody] PlayerPostDTO post, [FromQu
}
catch (ArgumentException e)
{
- return BadRequest(e);
+ ModelState.TryAddModelException(nameof(post), e);
+ return BadRequest(ModelState);
}
}
@@ -333,34 +344,37 @@ public async Task EditPost([FromBody] PlayerPostDTO post, [FromQu
/// Restrictions are in effect for the existing post.
/// Targeted post was not found.
[HttpDelete("{postId:guid}"), Authorize(RequireNoPlatformBans)]
- [ProducesResponseType(205), ProducesResponseType(typeof(string), 403), ProducesResponseType(typeof(string), 404)]
- public async Task DeletePost(Guid postId, [FromQuery] bool ignoreChecks = false)
+ public async Task DeletePost(Guid postId, [FromQuery] bool ignoreChecks = false)
{
if (_postService.GetPost(postId) is not { } post)
{
- return StatusCode(404, $"No post with ID {postId} found.");
+ ModelState.AddModelError(nameof(postId), $"No post with ID {postId} found.");
+ return NotFound(ModelState);
}
if (ignoreChecks)
{
if (!(User.IsInRole(ApiRoles.CM) || User.IsInRole(ApiRoles.Administrator)))
{
- return StatusCode(403, "Post Author is not authorized to bypass Post checks.");
+ ModelState.AddModelError(nameof(ignoreChecks), "Post Author is not authorized to bypass Post checks.");
+ return BadRequest(ModelState);
}
}
else
{
if (post.AuthorId != uint.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? throw new BadHttpRequestException("Missing NameIdentifier claim.")))
{
- return StatusCode(403, "Author is not authorized to delete posts on behalf of other users.");
+ ModelState.AddModelError(nameof(postId), "Author is not authorized to delete posts on behalf of other users.");
+ return BadRequest(ModelState);
}
if (post.ModLocked)
{
- return StatusCode(403, "Post has been locked by Community Managers. No deletion is possible.");
+ ModelState.AddModelError(nameof(postId), "Post has been locked by Community Managers. No deletion is possible.");
+ return BadRequest(ModelState);
}
}
await _postService.DeletePostAsync(postId);
- return StatusCode(205);
+ return StatusCode(StatusCodes.Status205ResetContent);
}
}
\ No newline at end of file
diff --git a/WowsKarma.Api/Controllers/ProfileController.cs b/WowsKarma.Api/Controllers/ProfileController.cs
index 3ce393a..cbd88ad 100644
--- a/WowsKarma.Api/Controllers/ProfileController.cs
+++ b/WowsKarma.Api/Controllers/ProfileController.cs
@@ -28,8 +28,8 @@ public ProfileController(PlayerService playerService, UserService userService)
/// Player ID to fetch profile flags from.
/// Returns player profile flags for given ID.
/// No player Profile was found.
- [HttpGet("{id}"), ProducesResponseType(typeof(UserProfileFlagsDTO), 200), ProducesResponseType(404)]
- public async Task GetProfileFlagsAsync(uint id) => await _playerService.GetPlayerAsync(id, true) is { } player
+ [HttpGet("{id}")]
+ public async Task> GetProfileFlagsAsync(uint id) => await _playerService.GetPlayerAsync(id, true) is { } player
? Ok(player.Adapt() with
{
PostsBanned = player.IsBanned(),
@@ -48,23 +48,24 @@ public async Task GetProfileFlagsAsync(uint id) => await _playerS
/// User cannot update a profile other than their own.
/// User profile was not found.
/// A cooldown is currently in effect for one of the values edited.
- [HttpPut, Authorize(RequireNoPlatformBans), ETag(false), ProducesResponseType(typeof(UserProfileFlagsDTO), 200)]
- [ProducesResponseType(423), ProducesResponseType(typeof(string), 403), ProducesResponseType(404)]
- public async Task UpdateProfileFlagsAsync([FromBody] UserProfileFlagsDTO flags)
+ [HttpPut, Authorize(RequireNoPlatformBans), ETag(false)]
+ public async Task UpdateProfileFlagsAsync([FromBody] UserProfileFlagsDTO flags)
{
try
{
if (flags.Id != User.ToAccountListing()!.Id && !User.IsInRole(ApiRoles.Administrator))
{
- return StatusCode(403, "User can only update their own profile.");
+ ModelState.AddModelError(nameof(flags.Id), "User can only update their own profile.");
+ return BadRequest(ModelState);
}
await _playerService.UpdateProfileFlagsAsync(flags);
- return Ok();
+ return NoContent();
}
catch (CooldownException e)
{
- return StatusCode(423, e);
+ ModelState.TryAddModelException(nameof(flags), e);
+ return StatusCode(StatusCodes.Status423Locked, ModelState);
}
catch (ArgumentException)
{
diff --git a/WowsKarma.Api/Controllers/ReplayController.cs b/WowsKarma.Api/Controllers/ReplayController.cs
index 70bebfc..1081184 100644
--- a/WowsKarma.Api/Controllers/ReplayController.cs
+++ b/WowsKarma.Api/Controllers/ReplayController.cs
@@ -49,37 +49,41 @@ ILogger logger
[HttpGet("{replayId:guid}"), ProducesResponseType(typeof(ReplayDTO), 200)]
public Task GetAsync(Guid replayId) => _ingestService.GetReplayDTOAsync(replayId);
- [HttpPost("{postId:guid}"), Authorize, RequestSizeLimit(ReplaysIngestService.MaxReplaySize), ProducesResponseType(201)]
- public async Task UploadReplayAsync(Guid postId, IFormFile replay, CancellationToken ct, [FromQuery] bool ignoreChecks = false)
+ [HttpPost("{postId:guid}"), Authorize, RequestSizeLimit(ReplaysIngestService.MaxReplaySize)]
+ public async Task UploadReplayAsync(Guid postId, IFormFile replay, CancellationToken ct, [FromQuery] bool ignoreChecks = false)
{
if (_postService.GetPost(postId) is not { } current)
{
- return StatusCode(404, $"No post with GUID {postId} found.");
+ ModelState.AddModelError("postId", $"No post with GUID {postId} found.");
+ return BadRequest(ModelState);
}
if (ignoreChecks)
{
if (!(User.IsInRole(ApiRoles.CM) || User.IsInRole(ApiRoles.Administrator)))
{
- return StatusCode(403, "Post Author is not authorized to bypass Replay checks.");
+ ModelState.AddModelError("ignoreChecks", "Only Community Managers and Administrators are allowed to bypass Replay checks.");
+ return BadRequest(ModelState);
}
}
else
{
if (current.AuthorId != uint.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? throw new InvalidOperationException("Missing NameIdentifier claim.")))
{
- return StatusCode(403, "Post Author is not authorized to edit post replays on behalf of other users.");
+ ModelState.AddModelError("postId", "Post Author is not authorized to upload replays on behalf of other users.");
+ return BadRequest(ModelState);
}
if (current.ModLocked)
{
- return StatusCode(403, "Specified Post has been locked by Community Managers. No modification is possible.");
+ ModelState.AddModelError("postId", "Specified Post has been locked by Community Managers. No modification is possible.");
+ return BadRequest(ModelState);
}
}
try
{
Replay ingested = await _ingestService.IngestReplayAsync(postId, replay, ct);
- return StatusCode(201, _ingestService.GetReplayDTOAsync(ingested.Id));
+ return CreatedAtAction("Get", new { replayId = ingested.Id }, _ingestService.GetReplayDTOAsync(ingested.Id));
}
catch (InvalidReplayException e)
{
@@ -89,7 +93,7 @@ public async Task UploadReplayAsync(Guid postId, IFormFile replay
catch (SecurityException se) when (se.Data["exploit"] is "CVE-2022-31265")
{
// Log this exception, and store the replay with the RCE the samples.
- _logger.LogWarning(se, "Replay upload failed due to CVE-2022-31265 exploit detection.");
+ _logger.LogWarning(se, "Replay upload failed due to CVE-2022-31265 exploit detection");
await _ingestService.IngestRceFileAsync(replay);
return BadRequest(se);
}
@@ -101,7 +105,7 @@ public async Task UploadReplayAsync(Guid postId, IFormFile replay
/// Start of date/time range
/// End of date/time range
[HttpPatch("reprocess/replay/all"), Authorize(Roles = ApiRoles.Administrator)]
- public IActionResult ReprocessPosts(DateTime start = default, DateTime end = default, CancellationToken ct = default)
+ public AcceptedResult ReprocessPosts(DateTime start = default, DateTime end = default)
{
if (start == default)
{
@@ -113,25 +117,24 @@ public IActionResult ReprocessPosts(DateTime start = default, DateTime end = def
end = DateTime.UtcNow;
}
- BackgroundJob.Enqueue(s => s.ReprocessAllReplaysAsync(start.ToUniversalTime(), end.ToUniversalTime(), ct));
- return StatusCode(202);
+ BackgroundJob.Enqueue(s => s.ReprocessAllReplaysAsync(start.ToUniversalTime(), end.ToUniversalTime(), HttpContext.RequestAborted));
+ return Accepted();
}
///
/// Triggers reporessing on a replay (Usable only by Administrators)
///
- ///
[HttpPatch("reprocess/replay/{replayId:guid}"), Authorize(Roles = ApiRoles.Administrator)]
- public IActionResult ReprocessReplay(Guid replayId, CancellationToken ct = default)
+ public IActionResult ReprocessReplay(Guid replayId)
{
try
{
- BackgroundJob.Enqueue(s => s.ReprocessReplayAsync(replayId, ct));
- return StatusCode(202);
+ BackgroundJob.Enqueue(s => s.ReprocessReplayAsync(replayId, HttpContext.RequestAborted));
+ return AcceptedAtAction("Get", routeValues: new { replayId }, null);
}
catch (ArgumentException)
{
- return StatusCode(404, $"No replay with GUID {replayId} found.");
+ return NotFound();
}
}
@@ -142,32 +145,30 @@ public IActionResult ReprocessReplay(Guid replayId, CancellationToken ct = defau
///
/// Whether to force rendering the minimap, even if it has already been rendered.
/// Whether to wait for the job to complete before returning.
- /// The cancellation token.
/// The minimap was rendered successfully.
/// The job was enqueued successfully.
/// No post with the specified GUID was found.
[HttpPatch("reprocess/minimap/{postId:guid}"), Authorize(Roles = ApiRoles.Administrator)]
- public async ValueTask RenderMinimap(Guid postId,
+ public async ValueTask RenderMinimap(Guid postId,
[FromServices] MinimapRenderingService minimapRenderingService,
[FromQuery] bool force = false,
- [FromQuery] bool waitForCompletion = false,
- CancellationToken ct = default
+ [FromQuery] bool waitForCompletion = false
) {
if (_postService.GetPost(postId) is not { } post)
{
- return StatusCode(404, $"No post with GUID {postId} found.");
+ return NotFound();
}
if (waitForCompletion)
{
- await minimapRenderingService.RenderPostReplayMinimapAsync(post.Id, force, ct);
+ await minimapRenderingService.RenderPostReplayMinimapAsync(post.Id, force, HttpContext.RequestAborted);
+ return Ok();
}
else
{
- BackgroundJob.Enqueue(s => s.RenderPostReplayMinimapAsync(post.Id, force, ct));
+ BackgroundJob.Enqueue(s => s.RenderPostReplayMinimapAsync(post.Id, force, HttpContext.RequestAborted));
+ return AcceptedAtAction("Get", routeValues: new { postId }, null);
}
-
- return StatusCode(waitForCompletion ? 200 : 202);
}
///
@@ -176,10 +177,9 @@ public async ValueTask RenderMinimap(Guid postId,
/// Start of date/time range
/// End of date/time range
/// Whether to force rendering a minimap, even if it has already been rendered.
- /// Cancellation token
/// The job was enqueued successfully.
[HttpPatch("reprocess/minimap/all"), Authorize(Roles = ApiRoles.Administrator)]
- public IActionResult RenderMinimaps(DateTime start = default, DateTime end = default, bool force = false, CancellationToken ct = default)
+ public AcceptedResult RenderMinimaps(DateTime start = default, DateTime end = default, bool force = false)
{
if (start == default)
{
@@ -191,7 +191,7 @@ public IActionResult RenderMinimaps(DateTime start = default, DateTime end = def
end = DateTime.UtcNow;
}
- BackgroundJob.Enqueue(s => s.ReprocessAllMinimapsAsync(start.ToUniversalTime(), end.ToUniversalTime(), force, ct));
- return StatusCode(202);
+ BackgroundJob.Enqueue(s => s.ReprocessAllMinimapsAsync(start.ToUniversalTime(), end.ToUniversalTime(), force, HttpContext.RequestAborted));
+ return Accepted();
}
}
diff --git a/WowsKarma.Api/Controllers/StatusController.cs b/WowsKarma.Api/Controllers/StatusController.cs
index 0cb983b..cd77c4a 100644
--- a/WowsKarma.Api/Controllers/StatusController.cs
+++ b/WowsKarma.Api/Controllers/StatusController.cs
@@ -15,15 +15,15 @@ public sealed class StatusController : Controller
///
/// Service is healthy.
[HttpGet, ProducesResponseType(200)]
- public IActionResult Status() => Ok();
+ public OkResult Status() => Ok();
[Route("/error"), ApiExplorerSettings(IgnoreApi = true)]
- public IActionResult HandleError()
+ public ObjectResult HandleError()
{
- if (HttpContext.Features.Get() is { Error: not null } exceptionHandlerFeature)
+ if (HttpContext.Features.Get() is { } exceptionHandlerFeature)
{
Uri fullPath = new UriBuilder(Request.Scheme, Request.Host.Host, Request.Host.Port ?? 80, exceptionHandlerFeature.Path).Uri;
-
+
return Problem(
detail: exceptionHandlerFeature.Error.StackTrace,
instance: fullPath.ToString(),