Added new API endpoints for bulk tagging and enriching shows, allowing for mass updates of genres and audience ratings. Implemented backend logic to handle bulk operations and updated the Dependency Injection configuration to include necessary services. Enhanced the frontend with new components for selecting shows and applying bulk actions, improving the user experience for managing multiple shows simultaneously. Localization updates were made to support these new features in both English and Russian.
241 lines
7.9 KiB
C#
241 lines
7.9 KiB
C#
using LiteCqrs;
|
|
using TeleWave.Api.Common;
|
|
using TeleWave.Application.Library;
|
|
using TeleWave.Application.Library.AddEpisode;
|
|
using TeleWave.Application.Library.BulkTagShows;
|
|
using TeleWave.Application.Library.CreateShow;
|
|
using TeleWave.Application.Library.DeleteShow;
|
|
using TeleWave.Application.Library.GetShow;
|
|
using TeleWave.Application.Library.ListShows;
|
|
using TeleWave.Application.Library.RemoveEpisode;
|
|
using TeleWave.Application.Library.RenameShow;
|
|
using TeleWave.Application.Library.SetShowAudience;
|
|
using TeleWave.Application.Library.SetShowGenres;
|
|
using TeleWave.Application.Library.SetShowOriginalName;
|
|
using TeleWave.Application.Metadata.EnrichShows;
|
|
using TeleWave.Domain.Library;
|
|
using TeleWave.Infrastructure.Identity;
|
|
|
|
namespace TeleWave.Api.Endpoints;
|
|
|
|
public static class ShowEndpoints
|
|
{
|
|
public static IEndpointRouteBuilder MapShowEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var admin = app.MapGroup("/api/admin/shows")
|
|
.WithTags("Admin.Shows")
|
|
.RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));
|
|
|
|
admin.MapPost("", CreateShow).Produces<CreatedIdResponse>(StatusCodes.Status201Created);
|
|
admin.MapGet("", ListShows).Produces<IReadOnlyList<ShowSummaryDto>>();
|
|
admin.MapGet("/{id:guid}", GetShow).Produces<ShowDto>();
|
|
admin.MapPut("/{id:guid}/name", Rename).Produces(StatusCodes.Status204NoContent);
|
|
admin
|
|
.MapPut("/{id:guid}/original-name", SetOriginalName)
|
|
.Produces(StatusCodes.Status204NoContent);
|
|
admin.MapPut("/{id:guid}/audience", SetAudience).Produces(StatusCodes.Status204NoContent);
|
|
admin.MapPut("/{id:guid}/genres", SetGenres).Produces(StatusCodes.Status204NoContent);
|
|
admin.MapDelete("/{id:guid}", DeleteShow).Produces(StatusCodes.Status204NoContent);
|
|
|
|
// Массовые операции над отмеченными шоу: разметка руками и обогащение из источника.
|
|
admin.MapPost("/bulk/tag", BulkTag).Produces<BulkTaggedResponse>();
|
|
admin.MapPost("/bulk/enrich", BulkEnrich).Produces<EnrichShowsResultDto>();
|
|
admin
|
|
.MapPost("/{id:guid}/episodes", AddEpisode)
|
|
.Produces<CreatedIdResponse>(StatusCodes.Status201Created);
|
|
admin
|
|
.MapDelete("/{id:guid}/episodes/{episodeId:guid}", RemoveEpisode)
|
|
.Produces(StatusCodes.Status204NoContent);
|
|
|
|
return app;
|
|
}
|
|
|
|
private static async Task<IResult> CreateShow(
|
|
CreateShowCommand command,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(command, cancellationToken);
|
|
return result.IsSuccess
|
|
? Results.Created(
|
|
$"/api/admin/shows/{result.Value}",
|
|
new CreatedIdResponse(result.Value)
|
|
)
|
|
: result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> ListShows(
|
|
ISender sender,
|
|
CancellationToken cancellationToken,
|
|
Guid? genreId = null,
|
|
bool interstitials = false
|
|
)
|
|
{
|
|
var result = await sender.Send(
|
|
new ListShowsQuery(genreId, interstitials),
|
|
cancellationToken
|
|
);
|
|
return Results.Ok(result);
|
|
}
|
|
|
|
private static async Task<IResult> GetShow(
|
|
Guid id,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(new GetShowQuery(id), cancellationToken);
|
|
return result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> Rename(
|
|
Guid id,
|
|
RenameShowBody body,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(new RenameShowCommand(id, body.Name), cancellationToken);
|
|
return result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> SetOriginalName(
|
|
Guid id,
|
|
SetShowOriginalNameBody body,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(
|
|
new SetShowOriginalNameCommand(id, body.OriginalName),
|
|
cancellationToken
|
|
);
|
|
return result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> SetAudience(
|
|
Guid id,
|
|
SetShowAudienceBody body,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(
|
|
new SetShowAudienceCommand(id, body.Audience),
|
|
cancellationToken
|
|
);
|
|
return result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> SetGenres(
|
|
Guid id,
|
|
SetShowGenresBody body,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(
|
|
new SetShowGenresCommand(id, body.GenreIds, body.PrimaryGenreId),
|
|
cancellationToken
|
|
);
|
|
return result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> DeleteShow(
|
|
Guid id,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(new DeleteShowCommand(id), cancellationToken);
|
|
return result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> AddEpisode(
|
|
Guid id,
|
|
AddEpisodeBody body,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(
|
|
new AddEpisodeCommand(id, body.MediaAssetId),
|
|
cancellationToken
|
|
);
|
|
return result.IsSuccess
|
|
? Results.Created($"/api/admin/shows/{id}", new CreatedIdResponse(result.Value))
|
|
: result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> RemoveEpisode(
|
|
Guid id,
|
|
Guid episodeId,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(new RemoveEpisodeCommand(id, episodeId), cancellationToken);
|
|
return result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> BulkTag(
|
|
BulkTagShowsBody body,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(
|
|
new BulkTagShowsCommand(
|
|
body.ShowIds,
|
|
body.GenreIds,
|
|
body.ReplaceGenres,
|
|
body.Audience,
|
|
body.SetAudience
|
|
),
|
|
cancellationToken
|
|
);
|
|
return result.IsSuccess
|
|
? Results.Ok(new BulkTaggedResponse(result.Value))
|
|
: result.ToHttpResult();
|
|
}
|
|
|
|
private static async Task<IResult> BulkEnrich(
|
|
BulkEnrichBody body,
|
|
ISender sender,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var result = await sender.Send(
|
|
new EnrichShowsMetadataCommand(body.ShowIds, body.Provider),
|
|
cancellationToken
|
|
);
|
|
return result.ToHttpResult();
|
|
}
|
|
}
|
|
|
|
public sealed record AddEpisodeBody(Guid MediaAssetId);
|
|
|
|
/// <summary>Массовая разметка: жанры и рейтинг применяются независимо друг от друга.</summary>
|
|
public sealed record BulkTagShowsBody(
|
|
IReadOnlyList<Guid> ShowIds,
|
|
IReadOnlyList<Guid>? GenreIds = null,
|
|
bool ReplaceGenres = false,
|
|
ShowAudience? Audience = null,
|
|
bool SetAudience = false
|
|
);
|
|
|
|
public sealed record BulkEnrichBody(IReadOnlyList<Guid> ShowIds, string Provider);
|
|
|
|
/// <summary>Скольких шоу коснулась массовая правка.</summary>
|
|
public sealed record BulkTaggedResponse(int Updated);
|
|
|
|
public sealed record RenameShowBody(string Name);
|
|
|
|
public sealed record SetShowOriginalNameBody(string? OriginalName);
|
|
|
|
/// <summary>Рейтинг шоу; null — снять проставленный.</summary>
|
|
public sealed record SetShowAudienceBody(ShowAudience? Audience);
|
|
|
|
public sealed record SetShowGenresBody(IReadOnlyList<Guid> GenreIds, Guid? PrimaryGenreId);
|