Files
TeleWave/backend/src/TeleWave.Api/Endpoints/MetadataEndpoints.cs
T

207 lines
6.7 KiB
C#

using LiteCqrs;
using TeleWave.Api.Common;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Images.DeleteImage;
using TeleWave.Application.Images.UploadImage;
using TeleWave.Application.Metadata;
using TeleWave.Application.Metadata.ApplyShowMetadata;
using TeleWave.Application.Metadata.ClearShowMetadata;
using TeleWave.Application.Metadata.FindMissingEpisodes;
using TeleWave.Application.Metadata.GetProviders;
using TeleWave.Application.Metadata.RefreshEpisodes;
using TeleWave.Application.Metadata.SearchShows;
using TeleWave.Application.Metadata.SetShowPoster;
using TeleWave.Application.Metadata.UpdateShowMetadata;
using TeleWave.Domain.Images;
using TeleWave.Infrastructure.Identity;
namespace TeleWave.Api.Endpoints;
public static class MetadataEndpoints
{
private const long MaxPosterBytes = 10L * 1024 * 1024; // 10 МБ
private static readonly IReadOnlySet<string> PosterExtensions = new HashSet<string>(
StringComparer.OrdinalIgnoreCase
)
{
".jpg",
".jpeg",
".png",
".webp",
};
public static IEndpointRouteBuilder MapMetadataEndpoints(this IEndpointRouteBuilder app)
{
var admin = app.MapGroup("/api/admin/metadata")
.WithTags("Admin.Metadata")
.RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));
admin.MapGet("/providers", GetProviders).Produces<IReadOnlyList<string>>();
admin.MapGet("/search", Search).Produces<IReadOnlyList<MetadataCandidate>>();
admin.MapPost("/shows/{showId:guid}/apply", Apply).Produces(StatusCodes.Status204NoContent);
admin.MapPut("/shows/{showId:guid}", Update).Produces(StatusCodes.Status204NoContent);
admin.MapDelete("/shows/{showId:guid}", Clear).Produces(StatusCodes.Status204NoContent);
admin
.MapPut("/shows/{showId:guid}/poster", UploadPoster)
.Produces(StatusCodes.Status204NoContent);
admin
.MapPut("/shows/{showId:guid}/poster-image", SetPosterImage)
.Produces(StatusCodes.Status204NoContent);
admin.MapPost("/shows/{showId:guid}/refresh-episodes", RefreshEpisodes).Produces<int>();
admin
.MapGet("/shows/{showId:guid}/missing-episodes", FindMissing)
.Produces<MissingEpisodesReport>();
// Постеры шоу и кадры серий теперь в общем реестре и отдаются по /api/images/{id}.
return app;
}
private static async Task<IResult> GetProviders(
ISender sender,
CancellationToken cancellationToken
)
{
var providers = await sender.Send(new GetMetadataProvidersQuery(), cancellationToken);
return Results.Ok(providers);
}
private static async Task<IResult> Search(
string provider,
string query,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(
new SearchShowMetadataQuery(provider, query),
cancellationToken
);
return result.ToHttpResult();
}
private static async Task<IResult> Apply(
Guid showId,
ApplyMetadataBody body,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(
new ApplyShowMetadataCommand(showId, body.Provider, body.ExternalId),
cancellationToken
);
return result.ToHttpResult();
}
private static async Task<IResult> Update(
Guid showId,
UpdateMetadataBody body,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(
new UpdateShowMetadataCommand(showId, body.Description, body.Year),
cancellationToken
);
return result.ToHttpResult();
}
private static async Task<IResult> Clear(
Guid showId,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(new ClearShowMetadataCommand(showId), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> UploadPoster(
Guid showId,
string fileName,
HttpRequest request,
IImageStore imageStore,
ISender sender,
CancellationToken cancellationToken
)
{
var ext = Path.GetExtension(fileName).ToLowerInvariant();
if (
request.ContentLength is > MaxPosterBytes or 0 or null
|| !PosterExtensions.Contains(ext)
)
return MetadataErrors.InvalidPoster.ToProblem();
// Регистрируем постер в общем реестре (категория ShowPoster) и привязываем к шоу.
var created = await sender.Send(
new UploadImageCommand(ImageCategory.ShowPoster, ext, fileName),
cancellationToken
);
if (!created.IsSuccess)
return created.ToHttpResult();
try
{
await imageStore.SaveAsync(created.Value, ext, request.Body, cancellationToken);
}
catch
{
await sender.Send(new DeleteImageCommand(created.Value), cancellationToken);
throw;
}
var result = await sender.Send(
new SetShowPosterCommand(showId, created.Value),
cancellationToken
);
if (!result.IsSuccess)
await sender.Send(new DeleteImageCommand(created.Value), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> SetPosterImage(
Guid showId,
SetPosterImageBody body,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(
new SetShowPosterCommand(showId, body.ImageId),
cancellationToken
);
return result.ToHttpResult();
}
private static async Task<IResult> RefreshEpisodes(
Guid showId,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(
new RefreshShowEpisodesMetadataCommand(showId),
cancellationToken
);
return result.ToHttpResult();
}
private static async Task<IResult> FindMissing(
Guid showId,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(new FindMissingEpisodesQuery(showId), cancellationToken);
return result.ToHttpResult();
}
}
public sealed record ApplyMetadataBody(string Provider, string ExternalId);
public sealed record UpdateMetadataBody(string? Description, int? Year);
public sealed record SetPosterImageBody(Guid? ImageId);