207 lines
6.7 KiB
C#
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);
|