Files

33 KiB
Raw Permalink Blame History

photos Specification

Purpose

Загрузка, хранение в S3 и просмотр фотографий с генерацией вариантов и извлечением EXIF.

Requirements

Requirement: Загрузка фото

Пользователь с правом загрузки SHALL иметь возможность загружать медиа (изображения и видео) по одному и массово; система SHALL принимать ввод image/* и video/*.

Scenario: Успешная загрузка

  • WHEN участник с правом загрузки отправляет файл изображения
  • THEN система сохраняет оригинал и создаёт запись фото с метаданными

Scenario: Успешная загрузка видео

  • WHEN участник с правом загрузки отправляет видеофайл
  • THEN система сохраняет оригинал и создаёт медиа-запись с признаком видео

Scenario: Загрузка без права

  • WHEN наблюдатель пытается загрузить фото
  • THEN система отклоняет запрос

Scenario: Неподдерживаемый файл

  • WHEN загружается файл, не являющийся изображением или видео
  • THEN система отклоняет загрузку

Requirement: Варианты изображений

Система SHALL генерировать четыре варианта: thumb (200×200, квадратный кроп), medium (800×800, квадратный кроп), preview (1200×1200, вписанный) и оригинал, в WebP, и извлекать EXIF (дата, координаты, размеры).

Scenario: Генерация вариантов

  • WHEN фото загружено
  • THEN система асинхронно генерирует thumb, medium и preview и заполняет EXIF-поля

Scenario: Форма вариантов сетки

  • WHEN генерируются thumb и medium
  • THEN они обрезаны до квадрата, а preview вписан с сохранением пропорций

Requirement: Листинг фото с пагинацией

Система SHALL возвращать фото альбома с cursor-пагинацией.

Scenario: Пагинация по курсору

  • WHEN клиент запрашивает список с курсором и лимитом
  • THEN система возвращает не более лимита фото и курсор следующей страницы

Requirement: Получение фото по требованию

Клиент SHALL иметь возможность получить актуальные данные фото с подписанным URL, обслуживаемым бэкендом, независимо от бэкенда хранения. Подпись SHALL быть детерминированной в пределах окна действия, чтобы повторные выдачи одного и того же объекта давали идентичный URL.

Scenario: Выдача свежего URL

  • WHEN клиент запрашивает фото по id
  • THEN система возвращает подписанный URL бэкенда для просмотра

Scenario: Повторная выдача совпадает

  • WHEN один и тот же объект запрашивается повторно в пределах окна действия подписи
  • THEN система возвращает идентичный URL

Requirement: Скачивание одиночного фото

Любой участник, включая viewer, SHALL иметь возможность скачать одиночное фото.

Scenario: Скачивание фото

  • WHEN участник запрашивает скачивание фото
  • THEN система возвращает подписанный URL оригинала, обслуживаемый бэкендом

Requirement: Удаление фото

Автор SHALL иметь возможность удалить своё фото; владелец и админ SHALL иметь возможность удалить любое фото. Удаление SHALL помещать фото в корзину и SHALL не удалять объекты хранилища.

Scenario: Удаление своего фото

  • WHEN автор удаляет своё фото
  • THEN фото помещается в корзину, а объекты хранилища сохраняются

Scenario: Удаление чужого фото участником

  • WHEN участник с ролью member пытается удалить чужое фото
  • THEN система отклоняет запрос

Requirement: Обложка по умолчанию

При загрузке фото система SHALL назначать его обложкой альбома, если обложка ещё не задана, то есть обложкой становится первое загруженное фото. Когда фото, выбранное обложкой, покидает альбом (помещено в корзину или перенесено в другой альбом), система SHALL снять обложку.

Scenario: Первое фото

  • WHEN фото загружено, и у альбома нет обложки
  • THEN система назначает это фото обложкой альбома

Scenario: Обложка уже задана

  • WHEN фото загружено, а обложка уже задана
  • THEN система не меняет обложку

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 система отклоняет загрузку с явной ошибкой о превышении размера

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 фото исчезает из исходного альбома и появляется в целевом, объекты хранилища не меняются

Scenario: Копирование

  • WHEN пользователь копирует фото в другой альбом
  • THEN в целевом альбоме появляется новая запись, ссылающаяся на те же объекты хранилища

Requirement: Корзина фото

Удаление фото SHALL помещать его в корзину (мягкое удаление). Восстановление SHALL возвращать фото в его альбом. Окончательное удаление SHALL удалять запись, а объекты хранилища SHALL удаляться только когда на них не остаётся ссылок ни в одном альбоме. Окончательное удаление SHALL быть доступно и для фото без альбома (например, после удаления альбома).

Scenario: Удаление в корзину

  • WHEN пользователь удаляет фото
  • THEN фото пропадает из альбома и появляется в корзине

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 метка отвязывается от фото

Requirement: Кэширование объектов

Доставка объектов SHALL помечать ответы как кэшируемые, чтобы браузер переиспользовал уже полученные превью, а повторное открытие альбома не вызывало повторную загрузку неизменившихся объектов.

Scenario: Заголовки кэширования

  • WHEN клиент получает объект (оригинал, превью или thumb) по подписанному URL
  • THEN ответ содержит заголовок кэширования с ограниченным сроком годности

Scenario: Повторный показ без перезагрузки

  • WHEN сетка превью перерисовывается, а URL объектов не изменились
  • THEN браузер отдаёт объекты из кэша без повторного запроса к бэкенду

Requirement: Медиа-запись фото

Данные фото SHALL включать вид медиа (image | video), длительность для видео и адрес воспроизведения, когда видео-вариант готов; для изображений адрес воспроизведения отсутствует.

Scenario: Запись изображения

  • WHEN клиент получает данные изображения
  • THEN вид медиа — изображение, длительность и адрес воспроизведения отсутствуют

Scenario: Запись видео без готового варианта

  • WHEN клиент получает данные видео, а вариант ещё не готов
  • THEN вид медиа — видео, длительность присутствует, адрес воспроизведения отсутствует

Scenario: Запись видео с готовым вариантом

  • WHEN видео-вариант готов
  • THEN данные медиа содержат адрес воспроизведения

Requirement: Очистка корзины

Пользователь SHALL иметь возможность окончательно удалить все элементы корзины одним действием. Система SHALL удалять только те элементы, которые пользователь вправе удалить навсегда, и SHALL пропускать остальные. Операция SHALL возвращать число удалённых элементов.

Scenario: Очистка корзины

  • WHEN пользователь очищает корзину
  • THEN все элементы, которые он вправе удалить навсегда, удаляются

Scenario: Чужие элементы

  • WHEN в корзине есть фото, которые пользователь не вправе удалить навсегда
  • THEN они остаются в корзине, а остальные удаляются

Scenario: Пустая корзина

  • WHEN пользователь очищает пустую корзину
  • THEN операция завершается успешно, удалив ноль элементов

Requirement: Контрольная сумма оригинала

Данные фото SHALL включать контрольную сумму содержимого оригинала, чтобы клиенты могли сопоставлять локальные файлы с серверными фото и избегать повторной загрузки. Контрольная сумма SHALL быть детерминированной для одного и того же содержимого.

Scenario: Выдача контрольной суммы

  • WHEN клиент получает данные фото, у которого зарегистрирован объект-оригинал
  • THEN ответ содержит контрольную сумму оригинала

Scenario: Одинаковое содержимое

  • WHEN два фото ссылаются на объекты с одинаковым содержимым
  • THEN их контрольные суммы совпадают

Scenario: Контрольная сумма доступна в списке

  • WHEN клиент запрашивает список фото альбома
  • THEN контрольные суммы входят в данные полученных фото

Requirement: Неразрушающее редактирование фотографии

Система SHALL поддерживать неразрушающее редактирование фотографий. Параметры правок (свет и цвет) SHALL храниться в метаданных фото, а отредактированная версия SHALL создаваться как отдельные объекты (роли edit, edit_thumb, edit_medium, edit_preview) рядом с исходными вариантами. Рендер SHALL выполняться по исходному объекту фото, а не по его уменьшенным вариантам. Исходный объект SHALL оставаться неизменным, а скачивание SHALL отдавать оригинал. Сброс правок SHALL удалять и параметры, и отредактированную версию. Правки SHALL применяться только к изображениям.

Scenario: Сохранение правок

  • WHEN пользователь сохраняет правки фото (свет и/или цвет)
  • THEN параметры сохраняются в метаданных, отредактированная версия рендерится в фоне, а после рендера фото получает URL отредактированных вариантов

Scenario: Фоновая подготовка версий

  • WHEN правки фото сохранены
  • THEN система готовит варианты сетки и полноразмерную версию в фоне и сообщает о готовности

Scenario: Рендер по оригиналу

  • WHEN готовится отредактированная версия фото
  • THEN световые и цветовые правки применяются к пикселям исходного объекта, а не к его уменьшенной копии

Scenario: Неразрушачность

  • WHEN фото отредактировано
  • THEN исходный объект и его варианты остаются неизменными

Scenario: Сброс правок

  • WHEN пользователь сбрасывает правки
  • THEN параметры и отредактированная версия удаляются, а фото снова показывается в исходном виде

Scenario: Права на правки

  • WHEN правки сохраняет пользователь без прав на это фото
  • THEN система отклоняет запрос

Scenario: Редактирование видео

  • WHEN пользователь пытается отредактировать видео
  • THEN система отклоняет запрос

Scenario: Цветовые правки сохраняются и рендерятся

  • WHEN пользователь сохраняет цветовые правки фото: баланс белого (температура/оттенок), сочность (vibrance), насыщенность, цветовой микшер по оттенкам, цветокоррекцию (тени/средние тона/света, яркость зон, наложение, баланс) или поканальную коррекцию
  • THEN параметры цвета сохраняются в метаданных, отредактированная версия рендерится в фоне, а после рендера фото получает URL отредактированных вариантов

Scenario: Цветовые правки применяются к пикселям исходного объекта

  • WHEN готовится отредактированная версия фото с цветовыми правками
  • THEN цветовые правки применяются к пикселям исходного объекта после световых правок, а не к его уменьшенной копии

Scenario: Пустые цветовые правки не сохраняются

  • WHEN в цветовых параметрах заданы только наложение/баланс или оттенок колеса без силы и яркости
  • THEN правки считаются пустыми и фото не получает цветовую версию

Requirement: Пипетка баланса белого

Клиент SHALL позволять выбрать нейтральную точку на фото и SHALL устанавливать температуру и оттенок так, чтобы цвет этой точки стал нейтральным. Выборка SHALL браться из исходного (неотредактированного) превью, а пресет баланса белого SHALL переходить в «Пользовательский».

Scenario: Выбор нейтральной точки

  • WHEN пользователь включает пипетку и кликает по нейтральному участку фото
  • THEN температура и оттенок обновляются, и цвет выбранной точки становится нейтральным

Scenario: Пипетка доступна только в просмотрщике

  • WHEN фото не открыто в просмотрщике
  • THEN пипетка недоступна

Requirement: Живой предпросмотр цветовых правок

Клиент SHALL показывать результат цветовых правок в просмотрщике до завершения серверного рендера. После завершения рендера SHALL показываться серверная версия.

Scenario: Правки видны сразу

  • WHEN пользователь двигает цветовой контрол
  • THEN просмотрщик обновляет изображение без ожидания серверного рендера

Scenario: Серверная версия заменяет предпросмотр

  • WHEN серверный рендер цветовых правок завершён
  • THEN просмотрщик показывает отрендеренную версию

Requirement: Тоновая кривая

Тоновая кривая SHALL задаваться контрольными точками для каналов RGB, R, G и B. Крайние точки SHALL существовать по умолчанию, SHALL двигаться по обеим осям и SHALL задавать плоские участки вне себя. Кривая SHALL быть плавной и монотонной между точками; первый клик по полю SHALL добавлять точку между крайними. Сброс SHALL возвращать линейную кривую.

Scenario: Крайние узлы существуют с самого начала

  • WHEN пользователь открывает инструмент «Кривая»
  • THEN у каждого канала уже есть крайние точки, и их можно перетаскивать

Scenario: Добавление точки

  • WHEN пользователь кликает по свободному полю кривой
  • THEN между крайними точками добавляется новая точка, а крайние остаются на месте

Scenario: Подвижные крайние узлы

  • WHEN пользователь двигает крайнюю точку по x или y
  • THEN вне крайних точек кривая остаётся плоской, а между точками — плавной

Scenario: Плавная интерполяция

  • WHEN кривая сохранена и отрендерена
  • THEN предпросмотр и итоговое фото используют одну и ту же плавную монотонную кривую

Scenario: Встроенные пресеты

  • WHEN пользователь выбирает «Линейный», «Средний контраст» или «Сильный контраст»
  • THEN кривые всех четырёх каналов заменяются кривыми пресета (контраст — в RGB, R/G/B — линейные), а подпись показывает выбранный пресет

Scenario: Пользовательская правка

  • WHEN пользователь вручную меняет точки кривой
  • THEN подпись показывает «Пользовательский»

Scenario: Сброс

  • WHEN пользователь сбрасывает кривую
  • THEN текущий канал возвращается к линейной кривой с крайними точками

Scenario: Независимые каналы

  • WHEN пользователь выбирает канал R, G или B
  • THEN он меняет только выбранный канал, остальные каналы остаются прежними

Requirement: Переключение фото в редакторе

При переключении фото правая панель SHALL сохранять выбранную вкладку. Несохранённые правки SHALL сохраняться тому фото, для которого они сделаны, в том числе если пользователь уже переключился на другое фото. Ответы фоновых запросов SHALL NOT подменять текущее фото или его параметры.

Scenario: Вкладка не меняется

  • WHEN пользователь переключает фото
  • THEN выбранная вкладка правой панели остаётся прежней

Scenario: Правки уходят текущему фото

  • WHEN пользователь меняет настройки после переключения
  • THEN изменения сохраняются текущему фото, а не предыдущему

Scenario: Фоновое сохранение не подменяет фото

  • WHEN сохранение предыдущего фото завершается после переключения
  • THEN панель продолжает показывать текущее фото, а его параметры не меняются

Requirement: Пользовательские пресеты кривой

Пользователь SHALL сохранять текущие кривые всех каналов (RGB, R, G и B) как именованный пресет, применять, переименовывать и удалять его. Пресет SHALL содержать кривую для каждого из четырёх каналов. Пресеты SHALL храниться в профиле пользователя и SHALL быть приватными. Импорт и экспорт SHALL выполняться JSON-файлом; импорт SHALL добавлять пресеты в профиль.

Scenario: Сохранение пресета

  • WHEN пользователь сохраняет текущие кривые под именем
  • THEN пресет появляется в списке и сохраняется в профиле вместе с кривыми RGB, R, G и B

Scenario: Применение пресета

  • WHEN пользователь выбирает пользовательский пресет
  • THEN кривые всех четырёх каналов заменяются сохранёнными, а текущий канал не меняется

Scenario: Переименование и удаление

  • WHEN пользователь переименовывает или удаляет пресет
  • THEN список пресетов и профиль обновляются

Scenario: Приватность пресетов

  • WHEN другой пользователь открывает свои пресеты
  • THEN он не видит пресеты первого пользователя

Scenario: Экспорт пресетов

  • WHEN пользователь экспортирует пресеты
  • THEN он получает JSON-файл с пользовательскими пресетами и кривыми всех четырёх каналов

Scenario: Импорт пресетов

  • WHEN пользователь импортирует корректный JSON-файл
  • THEN пресеты добавляются или обновляются в его профиле

Scenario: Некорректный файл или пресет

  • WHEN имя или кривая хотя бы одного канала недопустимы
  • THEN система отклоняет пресет и не меняет профиль