Skip to content

Commit

Permalink
feat(ReplayController): Update RenderMinimap endpoint
Browse files Browse the repository at this point in the history
- Added a new HTTP PATCH endpoint `RenderMinimap` to the `ReplayController` class.
- The endpoint triggers minimap rendering on a post's replay and is only accessible by administrators.
- It accepts the ID of the post to render the replay's minimap for, as well as an optional parameter `force` to force rendering even if it has already been rendered.
- If no post with the specified GUID is found, it returns a 404 status code.
- The job to render the minimap is enqueued successfully and returns a 202 status code.

fix(UserService): Fix GetUserSeedTokenAsync method

- Fixed an issue in the `GetUserSeedTokenAsync` method of the `UserService` class.
- Replaced `await GetUserAsync(id)` with `await context.Users.FindAsync(id)` to retrieve user information from the database correctly.

feat(MinimapRenderingService): Add force parameter to RenderPostReplayMinimapAsync method

- Added a new optional parameter `force` to the `RenderPostReplayMinimapAsync` method of the `MinimapRenderingService` class.
- This parameter allows forcing rendering of the minimap even if it has already been rendered before.
- If not forced and either no post or a post with already rendered minimap is found, skipping minimap rendering occurs and logs are updated accordingly.

feat(MinimapRenderingService): Add force parameter to UploadReplayMinimapAsync method

- Added a new optional parameter `force` to the private method `_UploadReplayMinimapAsync`.
- This parameter allows forcing upload of the minimap even if it has already been uploaded before.
  • Loading branch information
SakuraIsayeki committed Jul 23, 2023
1 parent 6c913f0 commit 29b9943
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 27 deletions.
26 changes: 17 additions & 9 deletions WowsKarma.Api/Controllers/ReplayController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ namespace WowsKarma.Api.Controllers;


[ApiController, Route("api/[controller]")]
public class ReplayController : ControllerBase
public sealed class ReplayController : ControllerBase
{
private readonly ReplaysIngestService _ingestService;
private readonly ReplaysProcessService _processService;
private readonly PostService _postService;
private readonly ILogger<ReplayController> _logger;

public ReplayController(ReplaysIngestService ingestService, ReplaysProcessService processService, PostService postService, ILogger<ReplayController> logger)
{
public ReplayController(
ReplaysIngestService ingestService,
ReplaysProcessService processService,
PostService postService,
ILogger<ReplayController> logger
) {
_ingestService = ingestService;
_processService = processService;
_postService = postService;
Expand Down Expand Up @@ -137,26 +141,30 @@ public async Task<IActionResult> ReprocessReplayAsync(Guid replayId, Cancellatio
return StatusCode(404, $"No replay with GUID {replayId} found.");
}
}

/// <summary>
/// Triggers minimap rendering on a post's replay (Usable only by Administrators)
/// </summary>
/// <param name="postId">The ID of the post to render the replay's minimap for.</param>
/// <param name="postService"></param>
/// <param name="minimapRenderingService"></param>
/// <param name="force">Whether to force rendering the minimap, even if it has already been rendered.</param>
/// <param name="ct">The cancellation token.</param>
/// <response code="202">The job was enqueued successfully.</response>
/// <response code="404">No post with the specified GUID was found.</response>
[HttpPatch("reprocess/minimap/{postId:guid}")]//[Authorize(Roles = ApiRoles.Administrator)]
public IActionResult RenderMinimap(Guid postId,
[FromServices] PostService postService,
[HttpPatch("reprocess/minimap/{postId:guid}"), Authorize(Roles = ApiRoles.Administrator)]
public async ValueTask<IActionResult> RenderMinimap(Guid postId,
// [FromServices] MinimapRenderingService minimapRenderingService,
[FromQuery] bool force = false,
CancellationToken ct = default
) {
if (postService.GetPost(postId) is not { } post)
if (_postService.GetPost(postId) is not { } post)
{
return StatusCode(404, $"No post with GUID {postId} found.");
}

BackgroundJob.Enqueue<MinimapRenderingService>(s => s.RenderPostReplayMinimapAsync(post.Id, post.PlayerId, ct));
BackgroundJob.Enqueue<MinimapRenderingService>(s => s.RenderPostReplayMinimapAsync(post.Id, post.PlayerId, force, ct));
// await minimapRenderingService.RenderPostReplayMinimapAsync(post.Id, post.PlayerId, ct);
return StatusCode(202);
}
}
4 changes: 2 additions & 2 deletions WowsKarma.Api/Services/Authentication/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public async Task<IEnumerable<Claim>> GetUserClaimsAsync(uint id) => await GetUs

public async Task<Guid> GetUserSeedTokenAsync(uint id)
{
if (await GetUserAsync(id) is not { } user)
if (await context.Users.FindAsync(id) is not { } user)
{
user = new()
{
Expand All @@ -48,7 +48,7 @@ public async Task<Guid> GetUserSeedTokenAsync(uint id)
return user.SeedToken;
}

public async Task<bool> ValidateUserSeedTokenAsync(uint id, Guid seedToken) => await GetUserAsync(id) is { } user && user.SeedToken == seedToken;
public async Task<bool> ValidateUserSeedTokenAsync(uint id, Guid seedToken) => await context.Users.FindAsync(id) is { } user && user.SeedToken == seedToken;

public async Task RenewSeedTokenAsync(uint id)
{
Expand Down
35 changes: 20 additions & 15 deletions WowsKarma.Api/Services/MinimapRenderingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,38 +41,43 @@ IConfiguration configuration
string connectionString = configuration[$"API:{Startup.ApiRegion.ToRegionString()}:Azure:Storage:ConnectionString"];
BlobServiceClient serviceClient = new(connectionString);
_containerClient = serviceClient.GetBlobContainerClient(MinimapBlobContainer);

if (!_containerClient.Exists())
{
_containerClient.Create();
}
}

/// <summary>
/// Renders the minimap for the specified post's replay.
/// </summary>
/// <param name="postId">The ID of the post to render the minimap for.</param>
/// <param name="targetedPlayerId">(optional) The ID of a player to highlight/target on the minimap.</param>
/// <param name="force">Whether to force rendering the minimap, even if it has already been rendered.</param>
/// <param name="ct">The cancellation token.</param>
[Tag("minimap", "replay", "render"), JobDisplayName("Render replay minimap for post {0}")]
public async Task RenderPostReplayMinimapAsync(Guid postId, uint? targetedPlayerId = null, CancellationToken ct = default)
public async Task RenderPostReplayMinimapAsync(Guid postId, uint? targetedPlayerId = null, bool force = false, CancellationToken ct = default)
{
_logger.LogDebug("Rendering minimap for post {postId}.", postId);

Post post = await _context.Posts.Include(static r => r.Replay).FirstOrDefaultAsync(p => p.Id == postId, cancellationToken: ct)
?? throw new ArgumentException($"Post with ID {postId} does not exist.", nameof(postId));

if (post.Replay is null or { MinimapRendered: true })
if (!force && post.Replay is null or { MinimapRendered: true })
{
_logger.LogInformation("Skipping minimap rendering for post {postId}.", postId);
return;
}

await using MemoryStream ms = await _replaysIngestService.FetchReplayFileAsync(post.Replay.Id, ct);

_logger.LogDebug("Rendering minimap for post {postId} from replay {replayId}.", postId, post.Replay.Id);
ms.Position = 0;

_logger.LogDebug("Rendering minimap for post {postId} from replay {replayId}.", postId, post.Replay.Id);
byte[] response = await _client.RenderReplayMinimapAsync(ms.ToArray(), post.Replay.Id.ToString(), targetedPlayerId, ct);

_logger.LogDebug("Minimap rendered for post {postId} from replay {replayId}.", postId, post.Replay.Id);
await UploadReplayMinimapAsync(post.Replay.Id, response, force, ct);

await UploadReplayMinimapAsync(post.Replay.Id, response, ct);

post.Replay.MinimapRendered = true;
await _context.SaveChangesAsync(ct);
}
Expand All @@ -84,28 +89,28 @@ public async Task RenderPostReplayMinimapAsync(Guid postId, uint? targetedPlayer
/// <param name="content">The minimap video as a blob.</param>
/// <param name="ct">The cancellation token.</param>
/// <exception cref="ArgumentException">Thrown when no post was found.</exception>
public async ValueTask UploadReplayMinimapAsync(Guid replayId, byte[] content, CancellationToken ct = default)
private async ValueTask UploadReplayMinimapAsync(Guid replayId, byte[] content, bool force, CancellationToken ct = default)
{
_logger.LogDebug("Uploading minimap for replay {replayId}.", replayId);
_logger.LogDebug("Uploading minimap for replay {replayId} to Azure storage.", replayId);

Post post = await _context.Posts.Include(static r => r.Replay).FirstOrDefaultAsync(p => p.Replay.Id == replayId, ct)
?? throw new ArgumentException($"Post with replay ID {replayId} does not exist.", nameof(replayId));

if (post.Replay is null or { MinimapRendered: true })
if (!force && post.Replay is null or { MinimapRendered: true })
{
_logger.LogInformation("Skipping minimap upload for replay {replayId}.", replayId);
return;
}

_logger.LogDebug("Uploading minimap for replay {replayId} to Azure storage.", replayId);

await using MemoryStream ms = new(content);
ms.Position = 0;

await _containerClient.UploadBlobAsync(post.Replay.Id.ToString(), ms, ct);

await _containerClient.UploadBlobAsync($"{post.Replay.Id}.mp4", ms, ct);

_logger.LogDebug("Minimap uploaded for replay {replayId} to Azure storage.", replayId);
}

public async ValueTask ReprocessAllMinimapsAsync(DateTime? start, DateTime? end, CancellationToken ct)
public async ValueTask ReprocessAllMinimapsAsync(DateTime? start, DateTime? end, bool force = false, CancellationToken ct = default)
{
_logger.LogWarning("Started reprocessing all replay minimaps between {start:g} and {end:g}", start, end);

Expand All @@ -117,7 +122,7 @@ from replay in _context.Replays.Include(static r => r.Post)
await foreach (Replay replay in replays.AsAsyncEnumerable().WithCancellation(ct))
{
_logger.LogDebug("Reprocessing replay minimap {replayId}", replay.Id);
await RenderPostReplayMinimapAsync(replay.Id, replay.Post.PlayerId, ct);
await RenderPostReplayMinimapAsync(replay.Id, replay.Post.PlayerId, force, ct);
}
}

Expand Down
2 changes: 1 addition & 1 deletion WowsKarma.Api/Services/Replays/ReplaysIngestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public async Task<MemoryStream> FetchReplayFileAsync(Guid replayId, Cancellation

BlobClient blobClient = _containerClient.GetBlobClient(replay.BlobName);

await using MemoryStream ms = new();
MemoryStream ms = new();
await blobClient.DownloadToAsync(ms, ct);
ms.Position = 0;

Expand Down

0 comments on commit 29b9943

Please sign in to comment.