# Design
## Context
См. `proposal.md` для мотивации. Текущее состояние, важное для подхода:
- Авторизация — Bearer-токен (не cookies), поэтому `
` не может обращаться к
защищённым эндпоинтам напрямую. Все фото уже отдаются фронтенду как presigned
URL (`internal/photos/service.go`, `attachURLs`).
- `albums.cover_photo_id` — колонка `text` без внешнего ключа (миграция
`000001_init.up.sql:15`), поэтому удаление фото-обложки оставляет висячий id.
- `albums.Service` уже держит `*storage.Client` и умеет presign; отдельной
зависимости от пакета `photos` нет.
- Сборка фото идёт асинхронно (`processVariants`): `thumb_key` появляется не
сразу после создания записи фото.
- В `backend/static` закоммичен собранный `Icon.svelte` (inline-SVG спрайт
Material Symbols), при этом в `frontend/src` компонента нет — UI сейчас на
эмодзи. Берём inline-SVG как ориентир.
## Goals / Non-Goals
**Goals:**
- Список альбомов отдаёт `cover_url` (presigned миниатюра) и карточки его рисуют.
- Выбор и очистка обложки из фото альбома в настройках, применяется немедленно.
- Автоматическая обложка не затирает ручной выбор.
- Кнопка создания — иконка с хинтом, открывающая диалог ввода имени.
**Non-Goals:**
- Обложка в шапке страницы альбома и в публичных share-страницах.
- Смена обложки из лайтбокса/контекстного меню фото (может быть позже).
- Переработка всей иконочной системы проекта — вводим минимальный inline-SVG.
## Decisions
### 1. `cover_url` в ответе списка (вариант A)
`GET /albums` отдаёт каждому альбому `cover_url` — presigned URL миниатюры
(`thumb`) или `null`. Источник ключа: `albums.Store` расширяет выборку
подзапросом к `photos` за `thumb_key` по `cover_photo_id`; `albums.Service`
делает presign через уже имеющийся `storage.Client`.
**Альтернативы:** (B) фронт дёргает `GET /photos/:id` на каждую обложку — N+1 и
тянет лишние поля; (C) эндпоинт `GET /albums/:id/cover` с редиректом — не
работает, `
` не приложит Bearer-токен. Оба отпадают.
### 2. Presign в `albums`, а не в `photos`
Не вводим зависимость `albums -> photos`; достаточно подзапроса за ключом и
существующего `storage` в `albums.Service`. Дублирование кода presign
минимально и оправдано сохранением слоёв.
### 3. Авто-обложка только при пустой
`photos.Store.SetCover` меняет SQL на условный:
`UPDATE albums SET cover_photo_id = $2 WHERE id = $1 AND cover_photo_id IS NULL`.
Вызов остаётся в `processVariants` после `SetVariants`, чтобы ключ миниатюры уже
существовал. Это же сохраняет ручной выбор при последующих загрузках.
### 4. Валидация обложки при PATCH
`albums.Service.Update` при заданном `cover_photo_id` SHALL проверять, что фото
принадлежит этому альбому (иначе обложкой станет произвольный чужой id). Ошибка —
`BadRequest`/`NotFound`. `clear_cover` по-прежнему очищает.
### 5. Иконка — inline-SVG, диалог — нативный `