# 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 иметь возможность удалить любое фото. Удаление SHALL помещать фото в корзину и SHALL не удалять объекты хранилища. #### Scenario: Удаление своего фото - **WHEN** автор удаляет своё фото - **THEN** фото помещается в корзину, а объекты хранилища сохраняются #### Scenario: Удаление чужого фото участником - **WHEN** участник с ролью member пытается удалить чужое фото - **THEN** система отклоняет запрос ### Requirement: Обложка по умолчанию При обработке загруженного фото система SHALL назначать его обложкой альбома только если обложка ещё не задана. #### Scenario: Первое фото - **WHEN** фото обработано, и у альбома нет обложки - **THEN** система назначает это фото обложкой #### Scenario: Обложка уже задана - **WHEN** фото обработано, а обложка уже задана - **THEN** система не меняет обложку ### Requirement: Переименование фото Автор фото, владелец и админ альбома SHALL иметь возможность изменить имя файла фото; остальные SHALL получать отказ, а пустое имя SHALL отклоняться. #### Scenario: Переименование автором - **WHEN** автор меняет имя файла своего фото на непустое - **THEN** система сохраняет новое имя и транслирует изменение подписчикам альбома #### Scenario: Пустое имя - **WHEN** пользователь подтверждает пустое имя файла - **THEN** система отклоняет запрос #### Scenario: Нет прав - **WHEN** участник без прав переименовывает чужое фото - **THEN** система отклоняет запрос ### Requirement: Ограничение размера загрузки Система SHALL ограничивать размер загружаемых файлов и SHALL отвечать понятной ошибкой при превышении лимита, а не ошибкой разбора формы. #### Scenario: Превышение лимита - **WHEN** загружаемый файл превышает максимальный размер - **THEN** система отклоняет загрузку с явной ошибкой о превышении размера #### Scenario: Файл в пределах лимита - **WHEN** файл не превышает лимит - **THEN** система принимает multipart-загрузку и обрабатывает фото ### Requirement: Метаданные фото (EXIF) При загрузке фото система SHALL извлекать распознаваемые EXIF-атрибуты и SHALL возвращать их вместе с данными фото; при отсутствии метаданных SHALL возвращать пустой набор. #### Scenario: Фото с метаданными - **WHEN** загружается фото с EXIF - **THEN** данные фото содержат извлечённые атрибуты (камера, экспозиция, ISO, дата съёмки и др.) #### Scenario: Фото без метаданных - **WHEN** загружается фото без читаемых EXIF - **THEN** данные фото не содержат атрибутов, а загрузка выполняется успешно ### Requirement: Перенос и копирование фото между альбомами Пользователь с правом удаления в исходном альбоме и правом загрузки в целевом SHALL иметь возможность перенести или скопировать фото в другой альбом. Операции SHALL изменять только метаданные и SHALL не изменять объекты хранилища. #### Scenario: Перенос - **WHEN** пользователь переносит фото в другой альбом - **THEN** фото исчезает из исходного альбома и появляется в целевом, объекты S3 не меняются #### Scenario: Копирование - **WHEN** пользователь копирует фото в другой альбом - **THEN** в целевом альбоме появляется новая запись, ссылающаяся на те же объекты S3 ### Requirement: Корзина фото Удаление фото SHALL помещать его в корзину (мягкое удаление). Восстановление SHALL возвращать фото в его альбом. Окончательное удаление SHALL удалять запись, а объекты хранилища SHALL удаляться только когда на них не остаётся ссылок ни в одном альбоме. #### Scenario: Удаление в корзину - **WHEN** пользователь удаляет фото - **THEN** фото пропадает из альбома и появляется в корзине #### Scenario: Восстановление - **WHEN** пользователь восстанавливает фото из корзины - **THEN** фото возвращается в свой альбом #### Scenario: Окончательное удаление без ссылок - **WHEN** пользователь удаляет фото из корзины навсегда, и на те же объекты нет ссылок в других альбомах - **THEN** запись и объекты хранилища удаляются #### Scenario: Окончательное удаление при наличии ссылок - **WHEN** на объект хранилища ссылается копия в другом альбоме - **THEN** запись удаляется, а объект хранилища сохраняется ### Requirement: Удаление альбома и восстановление фото без альбома Удаление альбома SHALL помещать его фото в корзину, не удаляя объекты хранилища. Если при восстановлении фото его альбом отсутствует, система SHALL предложить выбрать существующий альбом или создать новый. #### Scenario: Удаление альбома - **WHEN** владелец удаляет альбом - **THEN** его фото оказываются в корзине без привязки к альбому, файлы сохраняются #### Scenario: Восстановление без альбома - **WHEN** пользователь восстанавливает фото, у которого нет альбома - **THEN** система предлагает выбрать альбом или создать новый и восстанавливает фото в него ### Requirement: Метки фото Пользователь SHALL присваивать фото свои метки (область — пользователь): создавать новые и выбирать из списка с поиском; метки SHALL отдаваться вместе с фото и сниматься. #### Scenario: Создание метки - **WHEN** пользователь вводит новую метку и подтверждает - **THEN** метка создаётся и привязывается к фото #### Scenario: Выбор из списка с поиском - **WHEN** пользователь вводит часть названия метки - **THEN** показываются подходящие метки пользователя для выбора #### Scenario: Снятие метки - **WHEN** пользователь убирает метку с фото - **THEN** метка отвязывается от фото