namespace foodmarket.Api.Storage; /// Абстракция для хранилища бинарных файлов (картинки товаров, /// CSV-импорт и т.д.). Реализации: /// (файлы в {ContentRoot}/uploads/, доступны nginx'ом) и /// (S3-совместимый bucket). /// /// Принцип идентификации: key — относительный путь типа /// products/{productId:N}/{guid}.png. URL для публичной отдачи /// возвращает — для Local это /// /uploads/{key}, для Minio — тот же путь, но nginx уже его /// проксирует на API (см. GET /uploads/{**path}). public interface IObjectStorage { /// Сохранить поток. Возвращает key (то что /// каллер передал). Task SaveAsync(string key, Stream content, string contentType, CancellationToken ct = default); /// Открыть на чтение. null если объекта нет. Task<(Stream Stream, string ContentType)?> OpenAsync(string key, CancellationToken ct = default); /// Удалить. Не бросает если объекта уже нет. Task DeleteAsync(string key, CancellationToken ct = default); /// Публичный URL для отдачи через API/nginx. Не presigned — /// в обоих реализациях возвращает /uploads/{key}. Nginx/api /// прокси решает, откуда взять (диск или MinIO). string PublicUrl(string key) => $"/uploads/{key.TrimStart('/')}"; /// Имя реализации — для логов и диагностики. string Kind { get; } }