diff --git a/openspec/changes/shared-album/.openspec.yaml b/openspec/changes/archive/2026-10-10-shared-album/.openspec.yaml similarity index 100% rename from openspec/changes/shared-album/.openspec.yaml rename to openspec/changes/archive/2026-10-10-shared-album/.openspec.yaml diff --git a/openspec/changes/shared-album/design.md b/openspec/changes/archive/2026-10-10-shared-album/design.md similarity index 100% rename from openspec/changes/shared-album/design.md rename to openspec/changes/archive/2026-10-10-shared-album/design.md diff --git a/openspec/changes/shared-album/proposal.md b/openspec/changes/archive/2026-10-10-shared-album/proposal.md similarity index 100% rename from openspec/changes/shared-album/proposal.md rename to openspec/changes/archive/2026-10-10-shared-album/proposal.md diff --git a/openspec/changes/shared-album/specs/albums/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/albums/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/albums/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/albums/spec.md diff --git a/openspec/changes/shared-album/specs/auth/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/auth/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/auth/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/auth/spec.md diff --git a/openspec/changes/shared-album/specs/members/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/members/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/members/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/members/spec.md diff --git a/openspec/changes/shared-album/specs/notifications/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/notifications/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/notifications/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/notifications/spec.md diff --git a/openspec/changes/shared-album/specs/photos/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/photos/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/photos/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/photos/spec.md diff --git a/openspec/changes/shared-album/specs/realtime/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/realtime/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/realtime/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/realtime/spec.md diff --git a/openspec/changes/shared-album/specs/share-links/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/share-links/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/share-links/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/share-links/spec.md diff --git a/openspec/changes/shared-album/specs/social/spec.md b/openspec/changes/archive/2026-10-10-shared-album/specs/social/spec.md similarity index 100% rename from openspec/changes/shared-album/specs/social/spec.md rename to openspec/changes/archive/2026-10-10-shared-album/specs/social/spec.md diff --git a/openspec/changes/shared-album/tasks.md b/openspec/changes/archive/2026-10-10-shared-album/tasks.md similarity index 100% rename from openspec/changes/shared-album/tasks.md rename to openspec/changes/archive/2026-10-10-shared-album/tasks.md diff --git a/openspec/specs/albums/spec.md b/openspec/specs/albums/spec.md new file mode 100644 index 0000000..7c96668 --- /dev/null +++ b/openspec/specs/albums/spec.md @@ -0,0 +1,42 @@ +# albums Specification + +## Purpose + +Управление фотоальбомами: создание, просмотр, редактирование, удаление и владение. + +## Requirements + +### Requirement: Создание альбома +Зарегистрированный пользователь SHALL иметь возможность создать альбом и стать его владельцем. + +#### Scenario: Создание альбома +- **WHEN** пользователь создаёт альбом с названием +- **THEN** система создаёт альбом и назначает пользователя владельцем + +### Requirement: Список альбомов пользователя +Пользователь SHALL иметь возможность получить список альбомов, к которым у него есть доступ. + +#### Scenario: Список альбомов +- **WHEN** пользователь запрашивает список альбомов +- **THEN** система возвращает альбомы, где он владелец или участник + +### Requirement: Получение альбома +Участник SHALL иметь возможность получить данные альбома по id. + +#### Scenario: Просмотр альбома +- **WHEN** участник запрашивает альбом по id +- **THEN** система возвращает данные альбома + +### Requirement: Редактирование альбома +Владелец и админ SHALL иметь возможность редактировать название, описание и обложку альбома. + +#### Scenario: Редактирование альбома +- **WHEN** админ изменяет описание альбома +- **THEN** система сохраняет изменения + +### Requirement: Удаление альбома +Владелец SHALL иметь возможность удалить альбом вместе с содержимым. + +#### Scenario: Удаление альбома +- **WHEN** владелец удаляет альбом +- **THEN** система удаляет альбом, его фото, участников и share-ссылки diff --git a/openspec/specs/auth/spec.md b/openspec/specs/auth/spec.md new file mode 100644 index 0000000..7f06609 --- /dev/null +++ b/openspec/specs/auth/spec.md @@ -0,0 +1,47 @@ +# auth Specification + +## Purpose + +Регистрация и аутентификация пользователей по email и паролю с выдачей краткоживущих и долгоживущих JWT-токенов. + +## Requirements + +### Requirement: Регистрация пользователя +Пользователь SHALL иметь возможность зарегистрироваться, указав email, пароль и имя. Пароль SHALL храниться в виде bcrypt-хэша. + +#### Scenario: Успешная регистрация +- **WHEN** пользователь отправляет уникальный email, пароль и имя +- **THEN** система создаёт учётную запись и возвращает access- и refresh-токены + +#### Scenario: Повторный email +- **WHEN** пользователь регистрируется с уже занятым email +- **THEN** система возвращает ошибку конфликта и не создаёт вторую запись + +### Requirement: Вход по email и паролю +Пользователь SHALL иметь возможность войти, указав email и пароль. + +#### Scenario: Успешный вход +- **WHEN** пользователь вводит верные email и пароль +- **THEN** система возвращает access- и refresh-токены + +#### Scenario: Неверные учётные данные +- **WHEN** пользователь вводит неверный email или пароль +- **THEN** система возвращает 401, не раскрывая, какое именно поле неверно + +### Requirement: Обновление access-токена +Пользователь SHALL иметь возможность обменять refresh-токен на новую пару токенов. Access-токен живёт 15 минут, refresh-токен — 30 дней. + +#### Scenario: Валидный refresh-токен +- **WHEN** пользователь предоставляет действующий refresh-токен +- **THEN** система выдаёт новый access-токен и ротирует refresh-токен + +#### Scenario: Недействительный refresh-токен +- **WHEN** пользователь предоставляет истёкший или отозванный refresh-токен +- **THEN** система возвращает 401 + +### Requirement: Выход из системы +Пользователь SHALL иметь возможность выйти, отозвав refresh-токен. + +#### Scenario: Выход +- **WHEN** пользователь вызывает logout с действующим refresh-токеном +- **THEN** система отзывает refresh-токен, и его дальнейшее использование отклоняется diff --git a/openspec/specs/members/spec.md b/openspec/specs/members/spec.md new file mode 100644 index 0000000..f979c44 --- /dev/null +++ b/openspec/specs/members/spec.md @@ -0,0 +1,65 @@ +# members Specification + +## Purpose + +Управление составом альбома, ролями и правами доступа, включая приглашения по токену. + +## Requirements + +### Requirement: Роли участников +Участник альбома SHALL иметь одну из ролей: admin, editor, member, viewer. Владелец выводится из albums.owner_id и не хранится как роль участника. + +#### Scenario: Создатель становится владельцем +- **WHEN** пользователь создаёт альбом +- **THEN** система делает его владельцем альбома + +### Requirement: Матрица прав +Система SHALL ограничивать действия по ролям: загружать фото могут owner/admin/editor/member; удалять чужие фото — owner/admin; удалять свои — owner/admin/editor/member; комментировать — owner/admin/editor/member; скачивать одиночное фото и просматривать — все участники, включая viewer. + +#### Scenario: Наблюдатель не может загружать +- **WHEN** участник с ролью viewer пытается загрузить фото +- **THEN** система отклоняет запрос + +#### Scenario: Участник не может удалять чужие фото +- **WHEN** участник с ролью member пытается удалить фото другого автора +- **THEN** система отклоняет запрос + +### Requirement: Приглашение участников +Владелец и админ SHALL иметь возможность создавать инвайт-ссылку с токеном. Редактор SHALL иметь возможность приглашать только на роли member и viewer. Никто не может приглашать на роль выше своей. + +#### Scenario: Админ приглашает участника +- **WHEN** админ создаёт инвайт на роль member +- **THEN** система создаёт инвайт с токеном и сроком действия + +#### Scenario: Редактор не может приглашать админа +- **WHEN** редактор пытается создать инвайт на роль admin или editor +- **THEN** система отклоняет запрос + +### Requirement: Принятие инвайта +Пользователь SHALL иметь возможность стать участником, перейдя по токену инвайта. + +#### Scenario: Принятие действующего инвайта +- **WHEN** пользователь принимает неистёкший инвайт с неисчерпанным лимитом использования +- **THEN** система добавляет его в состав альбома с ролью из инвайта + +#### Scenario: Принятие истёкшего инвайта +- **WHEN** пользователь принимает истёкший инвайт +- **THEN** система отклоняет запрос + +### Requirement: Смена ролей +Админ SHALL иметь возможность менять роли editor/member/viewer. Владелец SHALL управлять ролями админов. Никто не может менять роль выше своей и не может трогать владельца. + +#### Scenario: Админ меняет роль участника +- **WHEN** админ повышает участника до editor +- **THEN** система обновляет роль + +#### Scenario: Админ не может назначить админа +- **WHEN** админ пытается назначить другому участнику роль admin +- **THEN** система отклоняет запрос + +### Requirement: Удаление участника +Владелец и админ SHALL иметь возможность удалять участников, кроме владельца и других админов. + +#### Scenario: Удаление участника +- **WHEN** админ удаляет участника с ролью member/viewer/editor +- **THEN** система убирает его из состава альбома diff --git a/openspec/specs/notifications/spec.md b/openspec/specs/notifications/spec.md new file mode 100644 index 0000000..1be1b64 --- /dev/null +++ b/openspec/specs/notifications/spec.md @@ -0,0 +1,28 @@ +# notifications Specification + +## Purpose + +Персональные уведомления пользователя о событиях (комментарии, лайки, участники) и их статус прочитано. + +## Requirements + +### Requirement: Создание уведомления +Система SHALL создавать уведомление пользователю при наступлении события (комментарий к его фото, лайк, добавление в альбом). + +#### Scenario: Комментарий к моему фото +- **WHEN** другой пользователь комментирует фото пользователя +- **THEN** система создаёт уведомление этому пользователю + +### Requirement: Список уведомлений +Пользователь SHALL иметь возможность получить свои уведомления. + +#### Scenario: Получение уведомлений +- **WHEN** пользователь запрашивает уведомления +- **THEN** система возвращает список с признаком прочитано + +### Requirement: Отметка прочитанным +Пользователь SHALL иметь возможность отметить уведомление прочитанным. + +#### Scenario: Отметить прочитанным +- **WHEN** пользователь отмечает уведомление прочитанным +- **THEN** система фиксирует время прочтения diff --git a/openspec/specs/photos/spec.md b/openspec/specs/photos/spec.md new file mode 100644 index 0000000..f49f173 --- /dev/null +++ b/openspec/specs/photos/spec.md @@ -0,0 +1,57 @@ +# photos Specification + +## Purpose + +Загрузка, хранение в S3 и просмотр фотографий с генерацией вариантов и извлечением EXIF. + +## Requirements + +### Requirement: Загрузка фото +Пользователь с правом загрузки SHALL иметь возможность загружать фото по одному и массово. + +#### Scenario: Успешная загрузка +- **WHEN** участник с правом загрузки отправляет файл изображения +- **THEN** система сохраняет оригинал и создаёт запись фото с метаданными + +#### Scenario: Загрузка без права +- **WHEN** наблюдатель пытается загрузить фото +- **THEN** система отклоняет запрос + +### Requirement: Варианты изображений +Система SHALL генерировать три варианта: thumb (200px), preview (1200px) и оригинал, с WebP для превью, и извлекать EXIF (дата, координаты, размеры). + +#### Scenario: Генерация вариантов +- **WHEN** фото загружено +- **THEN** система асинхронно генерирует thumb и preview и заполняет EXIF-поля + +### Requirement: Листинг фото с пагинацией +Система SHALL возвращать фото альбома с cursor-пагинацией. + +#### Scenario: Пагинация по курсору +- **WHEN** клиент запрашивает список с курсором и лимитом +- **THEN** система возвращает не более лимита фото и курсор следующей страницы + +### Requirement: Получение фото по требованию +Клиент SHALL иметь возможность получить актуальные данные фото с presigned URL. + +#### Scenario: Выдача свежего URL +- **WHEN** клиент запрашивает фото по id +- **THEN** система возвращает свежий presigned URL (срок до 1 часа) для просмотра + +### Requirement: Скачивание одиночного фото +Любой участник, включая viewer, SHALL иметь возможность скачать одиночное фото. + +#### Scenario: Скачивание фото +- **WHEN** участник запрашивает скачивание фото +- **THEN** система возвращает presigned URL оригинала + +### Requirement: Удаление фото +Автор SHALL иметь возможность удалить своё фото; владелец и админ SHALL иметь возможность удалить любое фото. + +#### Scenario: Удаление своего фото +- **WHEN** автор удаляет своё фото +- **THEN** система удаляет запись и файлы в S3 + +#### Scenario: Удаление чужого фото участником +- **WHEN** участник с ролью member пытается удалить чужое фото +- **THEN** система отклоняет запрос diff --git a/openspec/specs/realtime/spec.md b/openspec/specs/realtime/spec.md new file mode 100644 index 0000000..5235949 --- /dev/null +++ b/openspec/specs/realtime/spec.md @@ -0,0 +1,49 @@ +# realtime Specification + +## Purpose + +Поток событий альбома и персональный поток через Server-Sent Events. + +## Requirements + +### Requirement: Подписка на события альбома +Клиент SHALL иметь возможность подписаться на события конкретного альбома через SSE. + +#### Scenario: Подписка на альбом +- **WHEN** участник подключается к SSE-потоку альбома +- **THEN** система устанавливает text/event-stream и отправляет heartbeat + +### Requirement: События альбома +Система SHALL публиковать события photo.created, photo.deleted, comment.created, like.added, like.removed, member.joined, member.left, album.updated. + +#### Scenario: Новое фото +- **WHEN** в альбоме появляется фото +- **THEN** подписчики получают событие photo.created с метаданными и URL превью + +### Requirement: Персональный поток +Клиент SHALL иметь возможность подписаться на персональный поток (приглашения, уведомления). + +#### Scenario: Подписка на персональный поток +- **WHEN** пользователь подключается к персональному потоку +- **THEN** система отправляет ему персональные события + +### Requirement: Авторизация и область доступа +Клиент SHALL подписываться только на доступные ему альбомы; токен передаётся в query-параметре. + +#### Scenario: Подписка на недоступный альбом +- **WHEN** пользователь подписывается на альбом, к которому нет доступа +- **THEN** система отклоняет подключение + +### Requirement: Устойчивость соединения +Система SHALL отправлять heartbeat каждые 25 секунд и отключать медленных клиентов при переполнении буфера. + +#### Scenario: Переполнение буфера клиента +- **WHEN** буфер клиента переполняется +- **THEN** система отключает клиента + +### Requirement: Догон пропущенного +Клиент SHALL иметь возможность догнать пропущенные события через REST. + +#### Scenario: Переподключение +- **WHEN** клиент переподключается после разрыва +- **THEN** клиент догоняет события через запрос фото с параметром since diff --git a/openspec/specs/share-links/spec.md b/openspec/specs/share-links/spec.md new file mode 100644 index 0000000..8c5305f --- /dev/null +++ b/openspec/specs/share-links/spec.md @@ -0,0 +1,46 @@ +# share-links Specification + +## Purpose + +View-only доступ к альбому по ссылке без авторизации, с несколькими ссылками, сроком действия и отзывом. + +## Requirements + +### Requirement: Создание ссылки для просмотра +Владелец и админ SHALL иметь возможность создать ссылку для просмотра альбома с заданным сроком действия. + +#### Scenario: Создание ссылки +- **WHEN** админ создаёт share-ссылку со сроком действия +- **THEN** система создаёт токен и возвращает URL для просмотра + +### Requirement: Несколько ссылок +Альбом SHALL поддерживать несколько независимых share-ссылок. + +#### Scenario: Отзыв одной ссылки не затрагивает другие +- **WHEN** владелец отзывает одну ссылку +- **THEN** остальные ссылки остаются действующими + +### Requirement: Просмотр альбома по ссылке +Пользователь без авторизации SHALL иметь возможность просматривать альбом по действующему токену (сетка, лайтбокс, скачивание одиночного фото). + +#### Scenario: Просмотр по действующей ссылке +- **WHEN** аноним открывает ссылку с действующим токеном +- **THEN** система возвращает фото альбома со свежими presigned URL + +#### Scenario: Истёкшая или отозванная ссылка +- **WHEN** аноним открывает ссылку с истёкшим или отозванным токеном +- **THEN** система отклоняет доступ + +### Requirement: Ограничения по ссылке +Доступ по ссылке SHALL быть только на просмотр: без комментариев, лайков, загрузки и управления участниками. + +#### Scenario: Аноним не может комментировать +- **WHEN** аноним по ссылке пытается добавить комментарий +- **THEN** система отклоняет запрос + +### Requirement: Отзыв ссылки +Владелец и админ SHALL иметь возможность отозвать ссылку. + +#### Scenario: Отзыв ссылки +- **WHEN** владелец отзывает share-ссылку +- **THEN** система прекращает доступ по этому токену diff --git a/openspec/specs/social/spec.md b/openspec/specs/social/spec.md new file mode 100644 index 0000000..c781b1b --- /dev/null +++ b/openspec/specs/social/spec.md @@ -0,0 +1,39 @@ +# social Specification + +## Purpose + +Лайки и комментарии к фотографиям. + +## Requirements + +### Requirement: Лайк фото +Пользователь с правом комментирования SHALL иметь возможность поставить и убрать лайк. + +#### Scenario: Поставить лайк +- **WHEN** участник ставит лайк фото +- **THEN** система фиксирует лайк и публикует событие like.added + +#### Scenario: Убрать лайк +- **WHEN** участник убирает лайк +- **THEN** система удаляет лайк и публикует событие like.removed + +### Requirement: Комментарий к фото +Пользователь с правом комментирования SHALL иметь возможность добавить комментарий. + +#### Scenario: Добавить комментарий +- **WHEN** участник добавляет комментарий +- **THEN** система сохраняет комментарий и публикует событие comment.created + +### Requirement: Список комментариев +Система SHALL возвращать комментарии фото. + +#### Scenario: Получение комментариев +- **WHEN** клиент запрашивает комментарии фото +- **THEN** система возвращает список комментариев с авторами + +### Requirement: Удаление комментария +Автор комментария (или владелец/админ) SHALL иметь возможность удалить комментарий. + +#### Scenario: Удаление своего комментария +- **WHEN** автор удаляет свой комментарий +- **THEN** система удаляет комментарий