# Proposal ## Why S3-совместимое хранилище (Garage на `s3.localnet`) в проде недоступно извне, поэтому отдача медиа не может идти напрямую из хранилища в браузер. Нужен слой хранения, который всегда отдаёт блобы через бэкенд, и локальный бэкенд для инсталляций без S3. ## What Changes - Ввести интерфейс `storage.Backend`: Put/Delete/Get/Ping/EnsureBucket/Bucket, `URL` (всегда подписанный URL приложения) и обязательный `ServeObject`. - Реализовать бэкенд локальной ФС: блобы под корневым каталогом по непрозрачным ключам `objects/{id}/{role}{ext}`, защита от обхода пути, отдача через `ServeContent`. - Реализовать отдачу S3 через бэкенд: `ServeObject` тянет объект ranged `GetObject` из Garage (внутренняя сеть) и стримит браузеру; presigned URL больше не используются. - Публичный маршрут `/api/objects/` с HMAC-подписью, сроком, Range и Content-Type обслуживает и авторизованных, и анонимные share-ссылки. - Ввести `STORAGE_BACKEND=s3|fs` и `FS_ROOT`; убрать `S3_PUBLIC_URL`; переименовать `S3_PRESIGN_TTL` → `OBJECT_URL_TTL`; дефолт — `s3`. - Сделать метку хранилища в health динамической. - Сделать формулировки отдачи файлов нейтральными к бэкенду в спеках `photos` и `share-links`. ## Capabilities ### New Capabilities - `storage-backends`: выбор и работа объектного хранилища — S3-совместимое (Garage) и локальная ФС; блобы всегда отдаются через бэкенд по подписанному URL, хранилище клиенту не раскрывается. ### Modified Capabilities - `photos`: получение фото и скачивание не требуют presigned URL и обслуживаются бэкендом (подписанный URL). - `share-links`: просмотр по ссылке отдаёт подписанный URL, обслуживаемый бэкендом. ## Impact - `backend/internal/storage` (интерфейс, общий `Signer`, FS-бэкенд, S3-proxy-отдача), сервисы `photos` и `albums`, `app` (маршрут отдачи, health), `cmd/*`, `config`. - Удаление presign-пути AWS SDK из `storage`. - `.env.example`, `project.md`, `deploy/nginx-sse.conf`. - Спеки `photos`, `share-links`, новая `storage-backends`. - Не затрагивает слой метаданных.