chore(openspec): архивировать album-live-sync и синхронизировать основные спеки

- realtime: личный поток несёт события жизненного цикла альбома и изменения
  счётчика фото; добавлены требования о жизненном цикле, получателях, форме
  payload'а, догоне и потере доступа
- albums: photos_count считает только фото вне корзины
This commit is contained in:
iu
2026-10-15 23:25:07 +03:00
parent e82f45647c
commit 6e68242ec2
8 changed files with 556 additions and 2 deletions
@@ -0,0 +1,45 @@
# Tasks
## 1. Аудитория альбома и доставка в личный поток
- [x] 1.1 Добавить в `members.Repository` метод, возвращающий id пользователей альбома (владелец + участники) без join с `users`; реализовать в sqlite- и postgres-сторах; проверить новым тестом репозитория: `cd backend && go test ./internal/members/`
- [x] 1.2 Добавить в `albums.Service` публикацию события альбома в личные каналы получателей: перечитать альбом через `Store.GetByID` и обогатить `attachCover`, разослать по аудитории через `Hub.PublishUser`; закрыть доступ к резолверу аудитории интерфейсом для `members.Service`; проверить тестом с подпиской `Hub.SubscribeUser`, что получатель видит событие с тем же набором полей, что элемент `GET /api/albums`
- [x] 1.3 Проверить, что публикация в личный поток безопасна при отсутствии подписчиков и не затрагивает пер-альбомный канал: тест на хаб, где событие уходит пользователю без подписки, не паникует и не создаёт подписку, а подписчик канала альбома событие не получает; `cd backend && go test ./internal/sse/ ./internal/albums/`
## 2. Создание, метаданные и удаление альбома
- [x] 2.1 Публиковать `album.created` владельцу при создании альбома; проверить тестом, что владелец получает событие, а объект перечитан и содержит `photos_count` и `members_count`
- [x] 2.2 Публиковать `album.updated` владельцу и участникам при изменении названия, описания и обложки, сохранив существующую публикацию в канал альбома; проверить тестом, что обе подписки получают событие и payload совпадает по полям с ответом `GET /api/albums/:id`
- [x] 2.3 Публиковать `album.deleted` с идентификатором владельцу и участникам, вычисляя аудиторию до удаления альбома (состав удаляется каскадом); проверить тестом, что событие получено владельцем и участником, а после удаления `members.List` для альбома пуст
## 3. Состав участников
- [x] 3.1 При принятии приглашения публиковать `album.created` приглашённому и `album.updated` владельцу и остальным участникам; проверить тестом, что приглашённый получает ровно одно событие с объектом альбома, а владелец — обновлённый `members_count`
- [x] 3.2 При смене роли участника публиковать `album.updated` владельцу и участникам; проверить тестом, что событие получает и пользователь, чью роль изменили
- [x] 3.3 При исключении участника публиковать `album.updated` оставшимся и `album.deleted` с идентификатором исключённому; проверить тестом, что исключённый получает ровно одно событие и в нём нет объекта альбома
- [x] 3.4 Обновить документацию контракта в `project.md` (§11 «Real-time (SSE)» и §12 «SSE — детали реализации»): состав событий личного потока, форма payload'а, догон списка альбомов при переподключении; проверить, что перечисленные типы событий совпадают с реализованными публикациями, а описанные поля — с ответом `GET /api/albums`
## 4. Web-клиент
- [x] 4.1 Подписаться на `/api/sse/me` один раз на уровне layout'а `(app)`: `album.created` и `album.updated` применять через `albums.upsert`, `album.deleted` — через `albums.remove`, на `onReconnect` вызывать `albums.load()`; проверить `cd frontend && npm run check` и сценарием в двух вкладках: переименование и смена обложки отражаются без перезагрузки, порядок списка не меняется
- [x] 4.2 Обрабатывать потерю доступа на странице альбома: при событии удаления/исключения страница перестаёт отображать альбом и уходит на главную; проверить сценарием: владелец удаляет альбом, у участника с открытой страницей происходит переход на главную, а альбом исчезает из рейла
- [x] 4.3 Проверить жизненный цикл подписки: соединение создаётся один раз за сессию, не пересоздаётся при навигации между страницами и закрывается при выходе из аккаунта; проверить в DevTools (одно соединение `/api/sse/me` на сессию) и сценарием logout/login
## 5. Интеграционная проверка
- [x] 5.1 Сквозной сценарий на двух аккаунтах (два профиля браузера) на локальном сервере: создание альбома, приглашение, смена роли, исключение, удаление — список и открытая страница у второго пользователя обновляются без перезагрузки; проверить вручную и приложить результат (какие шаги и что наблюдалось)
- [x] 5.2 Проверить догон состояния: остановить сервер, изменить и удалить альбом, поднять сервер и убедиться, что после восстановления потока список на клиенте совпадает с `GET /api/albums` (удалённый альбом не возвращается); проверить сценарием и приложить вывод
- [x] 5.3 Прогнать на финальной ревизии `make test`, `cd frontend && npm run check` и `cd frontend && npm run build`; зафиксировать, что все три проходят без ошибок
## 6. Живое обновление состава фото
- [x] 6.1 В `albums.Service` добавить рассылку `album.updated` получателям личного потока без публикации в канал альбома (перечитать через `GetByID` + `attachCover`); закрыть метод интерфейсом для `photos.Service`; проверить тестом, что после вызова получатель видит событие, а подписчик канала альбома — нет
- [x] 6.2 Публиковать `photo.created` при загрузке фото и `album.updated` получателям при загрузке, копировании, перемещении, восстановлении, переводе в корзину и окончательном удалении; проверить тестом, что `photos_count` меняется у владельца и участника, а подписчик канала альбома получает `photo.created`
- [x] 6.3 Считать `photos_count` только по фото вне корзины: добавить `deleted_at IS NULL` в подзапросы счётчика в sqlite- и postgres-сторах; проверить тестом, что перевод в корзину уменьшает, а восстановление увеличивает счётчик
- [x] 6.4 Обновить `project.md` (§11/§12): состав фото в личном потоке, `photo.created` при загрузке, семантика `photos_count`
- [x] 6.5 Прогнать `make test`, `cd frontend && npm test`, `cd frontend && npm run check` и `cd frontend && npm run build`
## Workflow follow-up
- Заархивировать change после прохождения ревью проекта (`openspec archive album-live-sync`).
- Проверить, что после архивации дельта вошла в `openspec/specs/realtime/spec.md`, а `openspec validate --strict album-live-sync` не выдаёт ошибок.