Files
ds-galo/openspec/changes/archive/2026-10-15-album-live-sync/proposal.md
T
iu 6e68242ec2 chore(openspec): архивировать album-live-sync и синхронизировать основные спеки
- realtime: личный поток несёт события жизненного цикла альбома и изменения
  счётчика фото; добавлены требования о жизненном цикле, получателях, форме
  payload'а, догоне и потере доступа
- albums: photos_count считает только фото вне корзины
2026-10-15 23:25:07 +03:00

86 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
проверяется только при подключении, поэтому исключённый участник продолжает получать
события альбома до разрыва соединения.