Added tag/creator status update functionality on the API and UI layers.
All checks were successful
ci / build-test (push) Successful in 2m29s
ci / publish-image (push) Successful in 1m42s

This commit is contained in:
2026-04-25 14:17:13 -04:00
parent dbed9fc905
commit 204e186354
8 changed files with 182 additions and 34 deletions

View File

@@ -1,5 +1,8 @@
using JSMR.Application.Circles.Queries.Search;
using JSMR.Application.Creators.Commands.UpdateCreatorStatus;
using JSMR.Application.Creators.Queries.Search;
using JSMR.Application.Tags.Commands.SetEnglishName;
using JSMR.Application.Tags.Commands.UpdateTagStatus;
using JSMR.Application.Tags.Queries.Search;
using JSMR.Application.Users;
using JSMR.Application.VoiceWorks.Queries.Search;
@@ -56,6 +59,8 @@ public static class WebApplicationExtensions
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
app.MapSearchEndpoints();
app.MapTagCommandEndpoints();
app.MapCreatorCommandEndpoints();
app.MapAuthenticationEndpoints();
}
@@ -105,6 +110,39 @@ public static class WebApplicationExtensions
});
}
private static void MapTagCommandEndpoints(this WebApplication app)
{
app.MapPost("/api/tags/update-status", async (
UpdateTagStatusRequest request,
UpdateTagStatusHandler handler,
CancellationToken ct) =>
{
var result = await handler.HandleAsync(request, ct);
return Results.Ok(result);
});
app.MapPost("/api/tags/set-english-name", async (
SetTagEnglishNameRequest request,
SetTagEnglishNameHandler handler,
CancellationToken ct) =>
{
var result = await handler.HandleAsync(request, ct);
return Results.Ok(result);
});
}
private static void MapCreatorCommandEndpoints(this WebApplication app)
{
app.MapPost("/api/creators/update-status", async (
UpdateCreatorStatusRequest request,
UpdateCreatorStatusHandler handler,
CancellationToken ct) =>
{
var result = await handler.HandleAsync(request, ct);
return Results.Ok(result);
});
}
private static void MapAuthenticationEndpoints(this WebApplication app)
{
app.MapPost("/auth/login", async (LoginRequest req, IUserRepository users, HttpContext http) =>

View File

@@ -2,9 +2,9 @@
public record CreatorSearchItem
{
public int CreatorId { get; init; }
public required string Name { get; init; }
public bool Favorite { get; init; }
public bool Blacklisted { get; init; }
public int VoiceWorkCount { get; init; }
public int CreatorId { get; set; }
public required string Name { get; set; }
public bool Favorite { get; set; }
public bool Blacklisted { get; set; }
public int VoiceWorkCount { get; set; }
}

View File

@@ -1,4 +1,5 @@
using JSMR.Application.Circles.Queries.Search;
using JSMR.Application.Creators.Commands.UpdateCreatorStatus;
using JSMR.Application.Creators.Queries.Search;
using JSMR.Application.Scanning;
using JSMR.Application.Tags.Commands.SetEnglishName;
@@ -24,6 +25,7 @@ public static class ApplicationServiceCollectionExtensions
services.AddScoped<UpdateTagStatusHandler>();
services.AddScoped<SearchCreatorsHandler>();
services.AddScoped<UpdateCreatorStatusHandler>();
return services;
}

View File

@@ -2,10 +2,10 @@
public record TagSearchItem
{
public int TagId { get; init; }
public required string Name { get; init; }
public bool Favorite { get; init; }
public bool Blacklisted { get; init; }
public string? EnglishName { get; init; }
public int VoiceWorkCount { get; init; }
public int TagId { get; set; }
public required string Name { get; set; }
public bool Favorite { get; set; }
public bool Blacklisted { get; set; }
public string? EnglishName { get; set; }
public int VoiceWorkCount { get; set; }
}

View File

@@ -1,4 +1,5 @@
@using JSMR.UI.Blazor.Components
@using AntDesign
@using JSMR.UI.Blazor.Components
@using JSMR.UI.Blazor.Services
@inject SessionState Session
@@ -6,6 +7,8 @@
@inherits LayoutComponentBase
<AntContainer />
<MudLayout>
<MudAppBar Elevation="1" Dense="@_dense">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="MudBlazor.Color.Inherit" Edge="Edge.Start" OnClick="@ToggleDrawer" />
@@ -40,6 +43,7 @@
<MudMainContent Class="pt-18 px-8">
@Body
</MudMainContent>
@* Required *@
<MudThemeProvider @ref="_mudThemeProvider" @bind-IsDarkMode="_isDarkMode" />
<MudPopoverProvider />
@@ -49,10 +53,10 @@
@* Needed for snackbars *@
<MudSnackbarProvider />
</MudLayout>
<RadzenComponents @rendermode="RenderMode.InteractiveAuto" />
<AntContainer />
@code{
private bool _open = false;

View File

@@ -1,7 +1,10 @@
@page "/creators"
@inject VoiceWorksClient Client
@inject IJSRuntime JS
@using AntDesign
@using JSMR.Application.Common.Search
@using JSMR.Application.Creators.Commands.UpdateCreatorStatus
@using JSMR.Application.Creators.Contracts
@using JSMR.Application.Creators.Queries.Search
@using JSMR.Application.Creators.Queries.Search.Contracts
@using JSMR.UI.Blazor.Components
@@ -50,20 +53,29 @@
<AntDesign.Tag Color="AntDesign.TagColor.RedInverse">Blacklisted</AntDesign.Tag>
}
</AntDesign.PropertyColumn>
@* <AntDesign.ActionColumn HeaderStyle="width: 2em">
<AntDesign.Dropdown Trigger="@(new AntDesign.Trigger[] { AntDesign.Trigger.Click })">
<Overlay>
<AntDesign.Menu>
<AntDesign.MenuItem>
<span>Some Menu Item</span>
</AntDesign.MenuItem>
</AntDesign.Menu>
</Overlay>
<ChildContent>
<AntDesign.Button Shape="AntDesign.ButtonShape.Circle" Icon="@AntDesign.IconType.Outline.More" Type="AntDesign.ButtonType.Default"></AntDesign.Button>
</ChildContent>
</AntDesign.Dropdown>
</AntDesign.ActionColumn> *@
<AntDesign.ActionColumn HeaderStyle="width: 5em;" Style="text-align: center">
<Dropdown Trigger="@([Trigger.Click])">
<Overlay>
<Menu Selectable="false">
@if (!context.Favorite)
{
<MenuItem OnClick="(e) => SetStatus(context, CreatorStatus.Favorite)">Set as Favorite</MenuItem>
}
@if (!context.Blacklisted)
{
<MenuItem OnClick="(e) => SetStatus(context, CreatorStatus.Blacklisted)">Set as Blacklisted</MenuItem>
}
@if (context.Favorite || context.Blacklisted)
{
<MenuItem OnClick="(e) => SetStatus(context, CreatorStatus.Neutral)">Set as Neutral</MenuItem>
}
</Menu>
</Overlay>
<ChildContent>
<Button Icon="Ellipsis"></Button>
</ChildContent>
</Dropdown>
</AntDesign.ActionColumn>
</ColumnDefinitions>
</AntDesign.Table>
<JPagination2 PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" TotalItems="TotalItems" />
@@ -119,6 +131,9 @@
[Inject]
protected NavigationManager NavigationManager { get; set; } = default!;
[Inject]
INotificationService NotificationService { get; set; } = default!;
public string? Keywords { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 100;
@@ -259,4 +274,31 @@
NavigationManager.NavigateTo(uri);
}
private async Task SetStatus(CreatorSearchItem item, CreatorStatus status)
{
UpdateCreatorStatusRequest request = new(
CreatorId: item.CreatorId,
CreatorStatus: status
);
UpdateCreatorStatusResponse? response = await Client.UpdateCreatorStatusAsync(request);
if (response is not null)
{
item.Favorite = response.CreatorStatus is CreatorStatus.Favorite;
item.Blacklisted = response.CreatorStatus is CreatorStatus.Blacklisted;
}
await InvokeAsync(StateHasChanged);
var config = new NotificationConfig()
{
Message = $"Creator Status Update",
Description = $"Creator '{item.Name}' set to {status.ToString()}.",
Placement = NotificationPlacement.Top
};
await NotificationService.Open(config);
}
}

View File

@@ -3,6 +3,8 @@
@inject IJSRuntime JS
@using AntDesign
@using JSMR.Application.Common.Search
@using JSMR.Application.Tags.Commands.UpdateTagStatus
@using JSMR.Application.Tags.Contracts
@using JSMR.Application.Tags.Queries.Search
@using JSMR.Application.Tags.Queries.Search.Contracts
@using JSMR.UI.Blazor.Components
@@ -13,10 +15,6 @@
<PageTitle>Tags</PageTitle>
<div class="fdsfds">
@* <h1>Tags</h1> *@
@* <MudTextField T="string" Value="Keywords" ValueChanged="OnKeywordsChanged" Immediate="true" DebounceInterval="500" Label="Filter" Variant="MudBlazor.Variant.Text" Adornment="@Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Search" />
*@
<AntDesign.Card Title=@("Tags") Class="ant-blurred-card">
<Extra>
<AntDesign.Input TValue="string" Value="Keywords" ValueChanged="OnKeywordsChanged" DebounceMilliseconds="500" Placeholder="Filter">
@@ -53,14 +51,34 @@
<AntDesign.Tag Color="AntDesign.TagColor.RedInverse">Blacklisted</AntDesign.Tag>
}
</AntDesign.PropertyColumn>
<AntDesign.ActionColumn HeaderStyle="width: 5em;" Style="text-align: center">
<Dropdown Trigger="@([Trigger.Click])">
<Overlay>
<Menu Selectable="false">
@if (!context.Favorite)
{
<MenuItem OnClick="(e) => SetStatus(context, TagStatus.Favorite)">Set as Favorite</MenuItem>
}
@if (!context.Blacklisted)
{
<MenuItem OnClick="(e) => SetStatus(context, TagStatus.Blacklisted)">Set as Blacklisted</MenuItem>
}
@if (context.Favorite || context.Blacklisted)
{
<MenuItem OnClick="(e) => SetStatus(context, TagStatus.Neutral)">Set as Neutral</MenuItem>
}
</Menu>
</Overlay>
<ChildContent>
<Button Icon="Ellipsis"></Button>
</ChildContent>
</Dropdown>
</AntDesign.ActionColumn>
</ColumnDefinitions>
</AntDesign.Table>
<JPagination2 PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" TotalItems="TotalItems" />
</Body>
</AntDesign.Card>
@* <Pagination Current="PageNumber" OnChange="OnPaginationChange" PageSize="PageSize" Total="TotalItems" ShowTotal="ShowTotal"></Pagination>
<JPagination PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" TotalItems="TotalItems" /> *@
</div>
<style>
@@ -124,6 +142,9 @@
[Inject]
protected NavigationManager NavigationManager { get; set; } = default!;
[Inject]
INotificationService NotificationService { get; set; } = default!;
public string? Keywords { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 100;
@@ -280,4 +301,31 @@
NavigationManager.NavigateTo(uri);
}
private async Task SetStatus(TagSearchItem item, TagStatus status)
{
UpdateTagStatusRequest request = new(
TagId: item.TagId,
TagStatus: status
);
UpdateTagStatusResponse? response = await Client.UpdateTagStatusAsync(request);
if (response is not null)
{
item.Favorite = response.TagStatus is TagStatus.Favorite;
item.Blacklisted = response.TagStatus is TagStatus.Blacklisted;
}
await InvokeAsync(StateHasChanged);
var config = new NotificationConfig()
{
Message = $"Tag Status Update",
Description = $"Tag '{item.Name}' set to {status.ToString()}.",
Placement = NotificationPlacement.Top
};
await NotificationService.Open(config);
}
}

View File

@@ -1,5 +1,7 @@
using JSMR.Application.Circles.Queries.Search;
using JSMR.Application.Creators.Commands.UpdateCreatorStatus;
using JSMR.Application.Creators.Queries.Search;
using JSMR.Application.Tags.Commands.UpdateTagStatus;
using JSMR.Application.Tags.Queries.Search;
using JSMR.Application.VoiceWorks.Queries.Search;
using System.Net.Http.Json;
@@ -41,4 +43,16 @@ public class VoiceWorksClient(HttpClient http)
using var resp = await http.PostAsJsonAsync("/api/tags/search", request, ct);
return await resp.Content.ReadFromJsonAsync<SearchTagsResponse>(JsonOptions, cancellationToken: ct);
}
public async Task<UpdateTagStatusResponse?> UpdateTagStatusAsync(UpdateTagStatusRequest request, CancellationToken ct = default)
{
using var resp = await http.PostAsJsonAsync("/api/tags/update-status", request, ct);
return await resp.Content.ReadFromJsonAsync<UpdateTagStatusResponse>(JsonOptions, cancellationToken: ct);
}
public async Task<UpdateCreatorStatusResponse?> UpdateCreatorStatusAsync(UpdateCreatorStatusRequest request, CancellationToken ct = default)
{
using var resp = await http.PostAsJsonAsync("/api/creators/update-status", request, ct);
return await resp.Content.ReadFromJsonAsync<UpdateCreatorStatusResponse>(JsonOptions, cancellationToken: ct);
}
}