Files
ds-galo/openspec/specs/photos/spec.md
T
iu 0548b1ab5e feat(web): вкладка «Редактирование» с инструментом «Свет»
В правой панели фото появилась вкладка «Редактирование» с инструментом «Свет»:
ползунки экспозиции, контраста, светов, теней, белых и чёрных и точечная кривая
с выбором канала. Правки сохраняются автоматически, переключатель «Сброс»
удаляет параметры правок и отредактированную версию фото.

Редактирование неразрушающее: параметры хранятся в метаданных фото, а
отредактированная версия рендерится в фоне отдельным объектом (edit, edit_thumb,
edit_medium, edit_preview) рядом с исходными вариантами. Оригинал не меняется,
скачивание отдаёт исходный файл. Сетка, просмотр и обложки альбомов показывают
отредактированную версию, когда она отрендерена.
2026-10-20 23:06:24 +03:00

22 KiB
Raw 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 применять только к изображениям.

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

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

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

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

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

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

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

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

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

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