Add UploadLimits service and refactor user and media listing endpoints to use filter records for improved parameter handling. Update audio upload logic to streamline parameter passing and enhance clarity. Adjust ImageEndpoints to use HashSet for allowed extensions, and refine StreamingEndpoints to clarify token handling.
ci / build-backend (push) Successful in 1m29s
ci / build-frontend (push) Successful in 51s
ci / tests (push) Successful in 2m1s
ci / sonar (push) Successful in 4m22s

This commit is contained in:
Leonid Pershin
2026-07-26 23:05:37 +03:00
parent 0f0657e523
commit 69d1319564
7 changed files with 108 additions and 58 deletions
@@ -66,9 +66,7 @@ public static partial class ChannelEndpoints
}
private static async Task<IResult> UploadTemplateAudio(
Guid id,
Guid templateId,
string fileName,
[AsParameters] BumperAudioUpload upload,
HttpRequest request,
IBumperTemplateStorage storage,
IAudioProbe probe,
@@ -76,23 +74,31 @@ public static partial class ChannelEndpoints
CancellationToken cancellationToken
)
{
if (ResolveBumperExtension(fileName, request, BumperFiles.AudioExtensions) is not { } ext)
if (
ResolveBumperExtension(upload.FileName, request, BumperFiles.AudioExtensions)
is not { } ext
)
return ChannelErrors.InvalidBumperFile.ToProblem();
await storage.SaveAudioAsync(templateId, ext, request.Body, cancellationToken);
await storage.SaveAudioAsync(upload.TemplateId, ext, request.Body, cancellationToken);
// Длина заставки идёт по длине звука — замеряем ffprobe (при неудаче 0 → дефолтная длина).
var path = storage.AudioPath(templateId, ext);
var path = storage.AudioPath(upload.TemplateId, ext);
var duration = path is null
? null
: await probe.TryGetDurationAsync(path, cancellationToken);
var result = await sender.Send(
new SetBumperTemplateAudioCommand(id, templateId, ext, duration?.TotalSeconds ?? 0),
new SetBumperTemplateAudioCommand(
upload.Id,
upload.TemplateId,
ext,
duration?.TotalSeconds ?? 0
),
cancellationToken
);
if (!result.IsSuccess)
await storage.DeleteAudioAsync(templateId, cancellationToken);
await storage.DeleteAudioAsync(upload.TemplateId, cancellationToken);
return result.ToHttpResult();
}
@@ -242,13 +248,12 @@ public static partial class ChannelEndpoints
return Results.Text(sb.ToString(), "application/vnd.apple.mpegurl");
}
private static IResult PreviewSegment(
Guid id,
Guid templateId,
Guid variantId,
string file,
MediaPathResolver paths
)
/// <summary>
/// Сегмент превью. Канал и блок в маршруте есть, но хендлеру не нужны: каталог превью
/// адресуется подблоком (см. BumperPreview.AssetId), поэтому в сигнатуре их нет — незаявленные
/// параметры маршрута просто не связываются.
/// </summary>
private static IResult PreviewSegment(Guid variantId, string file, MediaPathResolver paths)
{
if (!SegmentFiles.IsSegmentName(file))
return Results.NotFound();
@@ -300,6 +305,13 @@ public sealed record UpdateBumperVariantBody(
int Weight
);
/// <summary>
/// Адрес загружаемого звука: канал и блок из маршрута плюс имя исходного файла из query (по нему
/// проверяется расширение). Свёрнуто в один параметр — кроме него хендлеру нужны ещё запрос, два
/// сервиса, диспетчер и токен отмены, и плоским списком сигнатура перестаёт читаться.
/// </summary>
public sealed record BumperAudioUpload(Guid Id, Guid TemplateId, string FileName);
/// <summary>Ограничения на загружаемый звук блока заставки (фон-картинка — через общий реестр).</summary>
internal static class BumperFiles
{