- photos: признак незавершённого рендера правок, мгновенный показ сброса, бесшовная подмена предпросмотра - realtime: события жизненного цикла рендера правок в photo.updated
200 lines
15 KiB
Markdown
200 lines
15 KiB
Markdown
# 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`
|