diff --git a/openspec/changes/add-user-avatar/.openspec.yaml b/openspec/changes/archive/2026-10-12-add-user-avatar/.openspec.yaml similarity index 100% rename from openspec/changes/add-user-avatar/.openspec.yaml rename to openspec/changes/archive/2026-10-12-add-user-avatar/.openspec.yaml diff --git a/openspec/changes/add-user-avatar/design.md b/openspec/changes/archive/2026-10-12-add-user-avatar/design.md similarity index 100% rename from openspec/changes/add-user-avatar/design.md rename to openspec/changes/archive/2026-10-12-add-user-avatar/design.md diff --git a/openspec/changes/add-user-avatar/proposal.md b/openspec/changes/archive/2026-10-12-add-user-avatar/proposal.md similarity index 100% rename from openspec/changes/add-user-avatar/proposal.md rename to openspec/changes/archive/2026-10-12-add-user-avatar/proposal.md diff --git a/openspec/changes/add-user-avatar/specs/media-objects/spec.md b/openspec/changes/archive/2026-10-12-add-user-avatar/specs/media-objects/spec.md similarity index 100% rename from openspec/changes/add-user-avatar/specs/media-objects/spec.md rename to openspec/changes/archive/2026-10-12-add-user-avatar/specs/media-objects/spec.md diff --git a/openspec/changes/add-user-avatar/specs/user-profile/spec.md b/openspec/changes/archive/2026-10-12-add-user-avatar/specs/user-profile/spec.md similarity index 100% rename from openspec/changes/add-user-avatar/specs/user-profile/spec.md rename to openspec/changes/archive/2026-10-12-add-user-avatar/specs/user-profile/spec.md diff --git a/openspec/changes/add-user-avatar/tasks.md b/openspec/changes/archive/2026-10-12-add-user-avatar/tasks.md similarity index 100% rename from openspec/changes/add-user-avatar/tasks.md rename to openspec/changes/archive/2026-10-12-add-user-avatar/tasks.md diff --git a/openspec/specs/media-objects/spec.md b/openspec/specs/media-objects/spec.md index 8cd66cd..3e8004e 100644 --- a/openspec/specs/media-objects/spec.md +++ b/openspec/specs/media-objects/spec.md @@ -56,8 +56,8 @@ MIME-типом, размером, габаритами и состоянием; - **THEN** изменяется только привязка к альбому, объекты хранилища не меняются ### Requirement: Сборка мусора по ссылкам -Объект хранилища SHALL удаляться только когда на него не ссылается ни одно фото; -удаление альбома и попадание фото в корзину SHALL сохранять объекты. +Объект хранилища SHALL удаляться только когда на него не ссылается ни одно фото и ни один +аватар пользователя; удаление альбома и попадание фото в корзину SHALL сохранять объекты. #### Scenario: Последняя ссылка удалена - **WHEN** фото удаляется навсегда и больше ни одно фото не ссылается на его объекты @@ -71,6 +71,10 @@ MIME-типом, размером, габаритами и состоянием; - **WHEN** владелец удаляет альбом - **THEN** объекты хранилища не удаляются +#### Scenario: Ссылка аватара +- **WHEN** на объект ссылается аватар пользователя +- **THEN** объект сохраняется в хранилище, пока ссылка существует + ### Requirement: Устойчивость жизненного цикла объекта Объект SHALL иметь состояние жизненного цикла, и незавершённые операции SHALL самовосстанавливаться: неудачная загрузка не оставляет осиротевших объектов, а @@ -110,3 +114,20 @@ SHALL различать короткие задания изображений #### Scenario: Длительное задание видео - **WHEN** выполняется длительная подготовка видео - **THEN** задание удерживается на весь срок выполнения и не вытесняет генерацию вариантов изображений + +### Requirement: Аватар пользователя как объект журнала +Аватар пользователя SHALL храниться как объект журнала, привязанный к пользователю по ролям, +соответствующим нарезкам аватара. Замена и удаление аватара SHALL менять привязку, а объекты +без ссылок SHALL освобождаться сборкой мусора. + +#### Scenario: Привязка аватара +- **WHEN** пользователь устанавливает аватар +- **THEN** объекты нарезок привязываются к пользователю по своим ролям + +#### Scenario: Замена аватара +- **WHEN** пользователь заменяет аватар +- **THEN** привязка указывает на новые объекты, а старые освобождаются сборкой мусора + +#### Scenario: Удаление аватара +- **WHEN** пользователь удаляет аватар +- **THEN** привязка снимается, и объекты аватара освобождаются сборкой мусора diff --git a/openspec/specs/user-profile/spec.md b/openspec/specs/user-profile/spec.md index dc3fe0a..8bee671 100644 --- a/openspec/specs/user-profile/spec.md +++ b/openspec/specs/user-profile/spec.md @@ -118,3 +118,89 @@ Android-приложение SHALL предоставлять отдельный #### Scenario: Возврат после удаления - **WHEN** удаление аккаунта завершилось успешно - **THEN** приложение очищает токены и возвращает на экран входа + +### Requirement: Загрузка фото профиля с выбором области +Пользователь SHALL загружать собственное фото в профиль, выбирая видимую квадратную область. +Сохранённый аватар SHALL соответствовать выбранной области. Загрузка SHALL быть доступна только +владельцу профиля. + +#### Scenario: Успешная загрузка +- **WHEN** пользователь выбирает изображение и подтверждает квадратную область +- **THEN** аватар обновляется и возвращается в профиле + +#### Scenario: Выбранная область учтена +- **WHEN** пользователь сдвигает или масштабирует область перед подтверждением +- **THEN** сохранённый аватар показывает именно выбранную область + +#### Scenario: Не изображение +- **WHEN** загруженный файл не является декодируемым изображением +- **THEN** система отклоняет запрос и не меняет аватар + +### Requirement: Нормализация аватара и снятие метаданных +Система SHALL сохранять аватар в виде квадратных изображений фиксированных размеров и SHALL NOT +сохранять EXIF-метаданные загруженного файла. + +#### Scenario: Метаданные не сохраняются +- **WHEN** загружено изображение с EXIF, включая GPS-координаты +- **THEN** сохранённый аватар не содержит EXIF и GPS + +#### Scenario: Слишком большой файл +- **WHEN** размер загруженного файла превышает допустимый предел +- **THEN** система отклоняет запрос и не меняет аватар + +### Requirement: Отдача аватара по стабильному URL +Профиль и публичные представления пользователя SHALL содержать URL аватара, когда он задан. +URL SHALL быть стабильным и SHALL изменяться при обновлении аватара, чтобы клиентские кэши +инвалидировались. Изображение SHALL отдаваться по публичной ссылке без аутентификации, чтобы +его могли загружать теги изображений и клиентские загрузчики. + +#### Scenario: Ссылка в представлениях пользователя +- **WHEN** аутентифицированный клиент запрашивает профиль, участников альбома, комментарии или автора фото +- **THEN** представления содержат URL аватара, если он задан + +#### Scenario: Кэш инвалидируется при замене +- **WHEN** пользователь заменяет аватар +- **THEN** URL аватара изменяется + +#### Scenario: Загрузка изображения без заголовка авторизации +- **WHEN** браузер или клиентский загрузчик изображений запрашивает изображение аватара по URL без заголовка авторизации +- **THEN** система отдаёт изображение + +### Requirement: Круглое отображение аватара в веб-интерфейсе +Веб-приложение SHALL отображать аватар пользователя круглым в шапке, на странице профиля, у +участников альбома, в комментариях и у автора фото. + +#### Scenario: Аватар в шапке +- **WHEN** аутентифицированный пользователь открывает любое место приложения +- **THEN** в шапке отображается его круглый аватар + +#### Scenario: Аватар рядом с другими пользователями +- **WHEN** пользователь просматривает участников, комментарии или автора фото +- **THEN** аватары этих пользователей отображаются круглыми + +### Requirement: Круглое отображение аватара в Android-клиенте +Android-приложение SHALL отображать аватар пользователя круглым на экране профиля и в шапке +списка альбомов. + +#### Scenario: Аватар на экране профиля +- **WHEN** пользователь открывает экран профиля +- **THEN** отображается его круглый аватар + +#### Scenario: Аватар в шапке списка альбомов +- **WHEN** пользователь открывает список альбомов +- **THEN** в шапке отображается его круглый аватар + +### Requirement: Заглушка при отсутствии аватара +Когда аватар не задан, система SHALL показывать нейтральную заглушку-силуэт вместо изображения. + +#### Scenario: Аватар не задан +- **WHEN** у пользователя нет аватара и его представление отображается в вебе или Android +- **THEN** вместо изображения показывается нейтральная круглая заглушка-силуэт + +### Requirement: Удаление аватара +Пользователь SHALL удалять свой аватар; после удаления SHALL показываться заглушка, а связанные +объекты хранилища SHALL освобождаться. + +#### Scenario: Сброс аватара +- **WHEN** пользователь удаляет аватар +- **THEN** представления возвращаются к заглушке, а объекты старого аватара освобождаются