using foodmarket.Api.Storage;
using Microsoft.AspNetCore.Mvc;
namespace foodmarket.Api.Controllers.Uploads;
/// Универсальный stream-endpoint для отдачи объектов из
/// IObjectStorage. Маршрут /uploads/{**path}. Для Local — читает с
/// диска, для MinIO — proxy'ит. Авторизация не требуется (картинки товаров
/// публичны как и было раньше); если в будущем понадобится защита — добавить
/// [Authorize] и подписать URL.
///
/// nginx должен по-прежнему перехватывать /uploads/ и проксировать на
/// API (см. deploy/nginx.conf). Сейчас локальный root монтируется в volume и
/// nginx раздаёт напрямую — этот контроллер становится альтернативой когда
/// MinIO активен.
[ApiController]
[Route("uploads")]
public class UploadsController : ControllerBase
{
private readonly IObjectStorage _storage;
public UploadsController(IObjectStorage storage) => _storage = storage;
[HttpGet("{*path}")]
public async Task Get(string path, [FromQuery] string? size, CancellationToken ct)
{
if (string.IsNullOrEmpty(path)) return NotFound();
// Sprint 14: ?size=thumb|medium|original. Запрос с size=thumb
// отдаёт .thumb.webp (если существует), иначе fallback
// на оригинал. Это позволяет фронту использовать с
// srcset для разных ширин экрана.
var variantSuffix = size?.ToLowerInvariant() switch
{
"thumb" => foodmarket.Api.Storage.ImageVariantService.ThumbSuffix,
"medium" => foodmarket.Api.Storage.ImageVariantService.MediumSuffix,
_ => "",
};
if (variantSuffix.Length > 0)
{
var variantPath = path + variantSuffix;
var variant = await _storage.OpenAsync(variantPath, ct);
if (variant is not null)
{
Response.Headers["Cache-Control"] = "public, max-age=2592000"; // 30 дней (агрессивнее для variant'ов)
return File(variant.Value.Stream, variant.Value.ContentType);
}
// Fallback на оригинал — старые загрузки до Sprint 14 не имеют variant'ов.
}
var obj = await _storage.OpenAsync(path, ct);
if (obj is null) return NotFound();
Response.Headers["Cache-Control"] = "public, max-age=604800"; // 7 дней
return File(obj.Value.Stream, obj.Value.ContentType);
}
}