- realtime: личный поток несёт события жизненного цикла альбома и изменения счётчика фото; добавлены требования о жизненном цикле, получателях, форме payload'а, догоне и потере доступа - albums: photos_count считает только фото вне корзины
86 lines
8.3 KiB
Markdown
86 lines
8.3 KiB
Markdown
# Proposal
|
||
|
||
## Why
|
||
|
||
Real-time сейчас работает только для открытого альбома: события изменений альбома публикуются
|
||
в канал конкретного альбома, поэтому у пользователя, который не смотрит этот альбом, список
|
||
альбомов (рейл, главная) остаётся устаревшим до перезагрузки страницы. Персональный поток
|
||
`GET /api/sse/me` существует, но не несёт событий жизненного цикла альбома, и ни один клиент
|
||
в него не подписан — то есть создание, переименование, смена обложки, изменение состава
|
||
участников и удаление альбома не доходят до тех, кого они касаются.
|
||
|
||
Отдельно устаревает состав фото: загрузка, перемещение, копирование, восстановление и удаление
|
||
фото не публикуют событие альбома, поэтому счётчик `photos_count` и обложка на карточках не
|
||
обновляются, а открытая страница альбома у других клиентов не показывает загруженное фото
|
||
(`Upload` не публикует `photo.created`, хотя способность `realtime` это требует).
|
||
|
||
## What Changes
|
||
|
||
- Персональный поток `GET /api/sse/me` начинает нести события жизненного цикла альбома:
|
||
`album.created` и `album.updated` с полным объектом альбома в той же форме, что отдаёт
|
||
`GET /api/albums`, и `album.deleted` с `{id}`.
|
||
- Получатели события — пользователи, для которых альбом входит в список доступных: владелец
|
||
(`albums.owner_id`) и участники (`album_members`); событие доставляется веером по личным
|
||
каналам.
|
||
- Публикации добавляются в: создание альбома, принятие инвайта, изменение названия/описания/
|
||
обложки, смену роли, исключение участника и выход, удаление альбома.
|
||
- Список альбомов обновляется и при изменении состава фото: загрузка, перемещение,
|
||
копирование, восстановление, перевод в корзину и окончательное удаление публикуют
|
||
`album.updated` в личные потоки владельца и участников, поэтому `photos_count` и обложка
|
||
на карточках становятся актуальными без перезагрузки.
|
||
- Загрузка фото публикует `photo.created` в канал альбома, поэтому открытая страница альбома
|
||
у других клиентов добавляет новое фото; готовность вариантов по-прежнему приходит как
|
||
`photo.updated` и обновляет фото на месте.
|
||
- Web-клиент подписывается на `/api/sse/me` на уровне layout'а приложения и применяет
|
||
события к стору списка альбомов (`upsert` / `remove`) без повторного запроса; при
|
||
переподключении догоняет состояние через `GET /api/albums`.
|
||
- Открытая страница альбома начинает обрабатывать `album.deleted` (сейчас обрабатывается
|
||
только `album.updated`, поэтому удалённый альбом продолжает отображаться у участников).
|
||
- Контракт существующих пер-альбомных событий (`photo.*`, `like.*`, `comment.created`,
|
||
`member.*`, `album.updated`) и REST не меняется: `Upload` начинает публиковать уже
|
||
предусмотренный контрактом `photo.created`, остальное — аддитивно.
|
||
|
||
**Не входит в этап** (осознанно отложено): персистентный центр уведомлений (колокольчик,
|
||
список непрочитанных, новые типы в таблице `notifications`); Android-клиент (личный поток
|
||
остаётся совместимым, подключение мобильного клиента — отдельный шаг); принудительный обрыв
|
||
пер-альбомного SSE у исключённого участника.
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
|
||
_Нет — поведение описывает расширение существующей способности `realtime`, отдельная
|
||
способность не вводится._
|
||
|
||
### Modified Capabilities
|
||
|
||
- `realtime`: персональный поток обязан нести события жизненного цикла альбома с полным
|
||
payload'ом; определяются получатели события, поведение клиента при получении (обновление
|
||
или удаление альбома в списке без запроса) и догон состояния при переподключении. Дополнительно
|
||
определяются события при изменении состава фото: список альбомов обновляется в реальном
|
||
времени, а загрузка фото публикует `photo.created`.
|
||
- `albums`: уточняется счётчик `photos_count` — он считает только доступные в альбоме фото
|
||
(вне корзины), чтобы совпадать с сеткой и корректно меняться при переводе в корзину и
|
||
восстановлении.
|
||
|
||
## Impact
|
||
|
||
- **Код (backend):** `internal/sse` (веер по личным каналам), `internal/albums/service.go`
|
||
(Create/Update/Delete, рассылка `album.updated` получателям), `internal/members/service.go`
|
||
(Accept/ChangeRole/Remove), `internal/photos/service.go` (загрузка/удаление/перемещение/
|
||
копирование/восстановление фото публикуют `photo.created` и `album.updated`), точка
|
||
сборки `internal/app/app.go` и `cmd/server/main.go` — если для веера потребуется новый
|
||
интерфейс-резолвер аудитории альбома.
|
||
- **Код (frontend):** `routes/(app)/+layout.svelte` (единая подписка на `/api/sse/me`),
|
||
`lib/stores/albums.svelte.ts` (upsert/remove/load как обработчики событий),
|
||
`routes/(app)/albums/[id]/+page.svelte` (обработка `album.deleted`), `lib/sse.ts` —
|
||
список известных событий.
|
||
- **API:** новых HTTP-эндпоинтов нет; существующий `GET /api/sse/me` начинает отдавать
|
||
дополнительные типы событий. Для старых клиентов изменение аддитивное.
|
||
- **Не затрагивается:** `notifications` (персистентные уведомления остаются как есть),
|
||
`android-client` (требование live-обновлений там и так ограничено активным экраном
|
||
альбома), схема БД, миграции, зависимости.
|
||
- **Известное ограничение, фиксируемое отдельной задачей:** доступ к пер-альбомному SSE
|
||
проверяется только при подключении, поэтому исключённый участник продолжает получать
|
||
события альбома до разрыва соединения.
|