POST /api/artwork/uploads (multipart/form-data) accepting logo and
watermark images. Validates content type (png/jpeg/gif/webp) and size
(SystemEnvironment.MaximumUploadMb, default 10MB) mirroring the Blazor
upload path; stores via IImageCache.SaveArtworkToCache; returns
{ path, contentType } consumable by channel create/update.
Includes handler + controller tests, OpenAPI 422 contract-test entry,
and regenerated v1.json.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
54 lines
2.1 KiB
C#
54 lines
2.1 KiB
C#
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Api.Artwork;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Interfaces.Images;
|
|
|
|
namespace ErsatzTV.Application.Artworks;
|
|
|
|
public class UploadArtworkHandler : IRequestHandler<UploadArtwork, Either<BaseError, ArtworkUploadResponseModel>>
|
|
{
|
|
// png/jpeg/gif/webp are all decoded by SkiaSharp and read by FFmpeg, matching the
|
|
// formats the Blazor logo/watermark upload already accepts. Format expansion is ersatztv#66.
|
|
private static readonly System.Collections.Generic.HashSet<string> AcceptedContentTypes = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"image/png",
|
|
"image/jpeg",
|
|
"image/gif",
|
|
"image/webp"
|
|
};
|
|
|
|
private readonly IImageCache _imageCache;
|
|
|
|
public UploadArtworkHandler(IImageCache imageCache) => _imageCache = imageCache;
|
|
|
|
public async Task<Either<BaseError, ArtworkUploadResponseModel>> Handle(
|
|
UploadArtwork request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
string contentType = (request.ContentType ?? string.Empty).Trim();
|
|
if (!AcceptedContentTypes.Contains(contentType))
|
|
{
|
|
return BaseError.New(
|
|
$"Unsupported image content type '{contentType}'; supported types are: {string.Join(", ", AcceptedContentTypes)}");
|
|
}
|
|
|
|
Either<BaseError, string> maybeFileName = await _imageCache.SaveArtworkToCache(
|
|
request.Stream,
|
|
request.ArtworkKind);
|
|
|
|
return maybeFileName.Map(fileName => new ArtworkUploadResponseModel(
|
|
BuildPath(request.ArtworkKind, fileName),
|
|
contentType));
|
|
}
|
|
|
|
// Mirror the on-disk conventions the Blazor editors use so the returned path is a drop-in
|
|
// for ArtworkContentTypeModel.Path: channel logos are addressed as "iptv/logos/{file}"
|
|
// (see ChannelEditor.UploadLogo), watermarks by the bare cache file name (see WatermarkEditor).
|
|
private static string BuildPath(ArtworkKind artworkKind, string fileName) =>
|
|
artworkKind switch
|
|
{
|
|
ArtworkKind.Logo => $"iptv/logos/{fileName}",
|
|
_ => fileName
|
|
};
|
|
}
|