Files
iu 4d23e76ab5 chore(openspec): архивировать fix-edit-preview-handoff и обновить photos, realtime
- photos: признак незавершённого рендера правок, мгновенный показ сброса,
  бесшовная подмена предпросмотра
- realtime: события жизненного цикла рендера правок в photo.updated
2026-10-25 10:04:08 +03:00

200 lines
15 KiB
Markdown
Raw Permalink 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.
# realtime Specification
## Purpose
Поток событий альбома и персональный поток через Server-Sent Events.
## Requirements
### Requirement: Подписка на события альбома
Клиент SHALL иметь возможность подписаться на события конкретного альбома через SSE.
#### Scenario: Подписка на альбом
- **WHEN** участник подключается к SSE-потоку альбома
- **THEN** система устанавливает text/event-stream и отправляет heartbeat
### Requirement: События альбома
Система SHALL публиковать события photo.created, photo.updated, photo.deleted,
comment.created, like.added, like.removed, member.joined, member.left, album.updated.
Событие photo.created SHALL обозначать появление фото в альбоме, а готовность вариантов
существующего фото SHALL публиковаться как photo.updated. Сохранение, готовность и сброс
правок фото SHALL также публиковаться как photo.updated с признаком незавершённого
рендера правок (`edit_pending`).
#### Scenario: Новое фото
- **WHEN** в альбоме появляется фото
- **THEN** подписчики получают событие photo.created с метаданными и URL превью
#### Scenario: Готовы варианты
- **WHEN** для существующего фото сгенерированы thumb и preview
- **THEN** подписчики получают событие photo.updated с этим фото
#### Scenario: Сохранены правки фото
- **WHEN** пользователь сохраняет правки фото, а рендер ещё не завершён
- **THEN** подписчики получают событие photo.updated с `edit_pending=true`
#### Scenario: Рендер правок готов
- **WHEN** серверный рендер сохранённых правок завершён
- **THEN** подписчики получают событие photo.updated с `edit_pending=false` и URL отредактированных вариантов
#### Scenario: Правки фото сброшены
- **WHEN** правки фото сброшены
- **THEN** подписчики получают событие photo.updated с `edit_pending=false` и без URL отредактированных вариантов
### Requirement: Персональный поток
Клиент SHALL иметь возможность подписаться на персональный поток (приглашения, уведомления,
изменения его альбомов).
#### Scenario: Подписка на персональный поток
- **WHEN** пользователь подключается к персональному потоку
- **THEN** система отправляет ему персональные события
#### Scenario: Изменение списка альбомов
- **WHEN** альбом, доступный пользователю, создан, изменён или удалён
- **THEN** пользователь получает соответствующее событие в персональном потоке
### Requirement: Авторизация и область доступа
Клиент SHALL подписываться только на доступные ему альбомы; токен передаётся в query-параметре.
#### Scenario: Подписка на недоступный альбом
- **WHEN** пользователь подписывается на альбом, к которому нет доступа
- **THEN** система отклоняет подключение
### Requirement: Устойчивость соединения
Система SHALL отправлять heartbeat каждые 25 секунд и отключать медленных клиентов при переполнении буфера.
#### Scenario: Переполнение буфера клиента
- **WHEN** буфер клиента переполняется
- **THEN** система отключает клиента
### Requirement: Догон пропущенного
Клиент SHALL иметь возможность догнать пропущенные события через REST.
#### Scenario: Переподключение
- **WHEN** клиент переподключается после разрыва
- **THEN** клиент догоняет события через запрос фото с параметром since
### Requirement: Различение добавления и обновления фото
Клиент SHALL различать добавление нового фото в альбом и обновление уже показанного:
обновление SHALL изменять фото на месте, не меняя порядок сетки.
#### Scenario: Обновление отображаемого фото
- **WHEN** приходит событие обновления для фото, которое уже показано
- **THEN** клиент обновляет его на месте, порядок сетки не меняется
#### Scenario: Обновление отсутствующего фото
- **WHEN** приходит событие обновления для фото, которого нет в текущем списке
- **THEN** клиент не добавляет его в сетку
### Requirement: События жизненного цикла альбома
Система SHALL публиковать в персональные потоки события `album.created` и `album.updated`
с объектом альбома и `album.deleted` с идентификатором альбома.
#### Scenario: Создание альбома
- **WHEN** пользователь создаёт альбом
- **THEN** владелец получает событие `album.created` с объектом альбома
#### Scenario: Изменение метаданных альбома
- **WHEN** участник с правом редактирования меняет название, описание или обложку
- **THEN** владелец и участники получают событие `album.updated` с актуальным объектом
#### Scenario: Удаление альбома
- **WHEN** владелец удаляет альбом
- **THEN** владелец и участники получают событие `album.deleted` с идентификатором
### Requirement: Получатели событий об альбоме
Система SHALL доставлять события жизненного цикла альбома владельцу и всем участникам
альбома, для которых он доступен.
#### Scenario: Веер по участникам
- **WHEN** альбом изменён
- **THEN** событие получает владелец и каждый участник альбома, включая инициатора изменения
#### Scenario: Принятие приглашения
- **WHEN** пользователь принимает приглашение в альбом
- **THEN** приглашённый получает событие о появлении альбома в своём списке, а владелец —
обновлённый объект альбома с новым числом участников
### Requirement: Единая форма объекта альбома в событиях
Объект альбома в событиях SHALL совпадать по форме с элементом ответа списка альбомов,
включая URL обложки и счётчики, чтобы клиент применял событие без дополнительных запросов.
#### Scenario: Объект события совпадает со списком
- **WHEN** клиент получает `album.created` или `album.updated`
- **THEN** объект содержит те же поля, что элемент ответа `GET /api/albums`
### Requirement: Применение события без повторного запроса
Клиент SHALL обновлять локальный список альбомов из payload'а события: создавать или
заменять альбом по полученному объекту и удалять его по идентификатору, не выполняя
дополнительный запрос списка.
#### Scenario: Обновление альбома из списка
- **WHEN** приходит событие обновления для альбома, который уже есть в списке клиента
- **THEN** клиент заменяет альбом по полученному объекту без дополнительного запроса списка
#### Scenario: Удаление альбома из списка
- **WHEN** приходит событие удаления для альбома, который есть в списке клиента
- **THEN** клиент убирает альбом из списка
#### Scenario: Порядок списка
- **WHEN** клиент применяет событие к списку альбомов
- **THEN** порядок элементов списка определяется правилами отображения этого клиента
### Requirement: Догон состояния альбомов при переподключении
Клиент SHALL после восстановления персонального потока синхронизировать список альбомов
через REST, чтобы отразить изменения, пропущенные во время разрыва соединения.
#### Scenario: Переподключение персонального потока
- **WHEN** персональный поток восстанавливается после разрыва
- **THEN** клиент перезапрашивает список альбомов и приводит локальное состояние
к серверному
### Requirement: Потеря доступа к альбому
Клиент SHALL переставать отображать альбом, который удалён или к которому потерян доступ,
при получении соответствующего персонального события, в том числе на открытой странице
альбома.
#### Scenario: Альбом удалён во время просмотра
- **WHEN** владелец удаляет альбом, пока участник просматривает его страницу
- **THEN** участник получает событие и страница перестаёт отображать удалённый альбом
#### Scenario: Участник исключён из альбома
- **WHEN** участника исключают из альбома
- **THEN** исключённый получает событие, и альбом убирается из его списка
### Requirement: Обновление альбома при изменении состава фото
Система SHALL публиковать `album.updated` с актуальным объектом альбома в персональные потоки
владельца и участников, когда состав фото альбома меняется: загрузка, копирование, перемещение,
восстановление, перевод в корзину и окончательное удаление. При загрузке система также SHALL
публиковать `photo.created` в поток альбома, чтобы открытая страница добавляла новое фото.
#### Scenario: Загрузка фото
- **WHEN** пользователь загружает фото в альбом
- **THEN** владелец и участники получают `album.updated` с увеличенным `photos_count`
#### Scenario: Перемещение фото
- **WHEN** фото перемещается из одного альбома в другой
- **THEN** владелец и участники обоих альбомов получают `album.updated` с актуальными счётчиками
#### Scenario: Фото в корзине
- **WHEN** фото переводится в корзину или восстанавливается
- **THEN** владелец и участники получают `album.updated` с обновлённым `photos_count`
#### Scenario: Окончательное удаление фото
- **WHEN** фото удаляется окончательно
- **THEN** владелец и участники получают `album.updated` с уменьшенным `photos_count`
### Requirement: Обновление альбома при готовности вариантов обложки
Когда для фото, являющегося обложкой альбома, сгенерированы варианты, система SHALL
публиковать `album.updated` с актуальным объектом альбома в персональные потоки владельца и
участников: только тогда объект получает ссылку на обложку. Готовность вариантов прочих фото
событий не порождает.
#### Scenario: Варианты обложки готовы
- **WHEN** для фото, которое является обложкой альбома, сгенерированы thumb и medium
- **THEN** владелец и участники получают `album.updated` с непустой ссылкой на обложку
#### Scenario: Варианты обычного фото готовы
- **WHEN** варианты готовы для фото, которое не является обложкой альбома
- **THEN** персональные потоки не получают `album.updated`