Files
ds-galo/openspec/changes/add-user-avatar/tasks.md
T
iu 04617d1cdb feat(profile): add round avatar with upload and cropping
- backend: user_avatar_objects binding (PG+SQLite), GC preserves avatar blobs,
  avatar service (EXIF-stripping WebP 128/512), public avatar read route
- web: avatar upload/crop on profile, round avatar in header/members/comments/author
- android: avatar upload/crop, round avatar on profile and albums header
- openspec: add-user-avatar proposal/design/specs/tasks
2026-10-12 16:16:47 +03:00

48 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks
## 1. Модель данных и сборка мусора
- [x] 1.1 Добавить миграцию Postgres `000014_user_avatar_objects.up.sql`/`.down.sql` и SQLite `000006_user_avatar_objects.up.sql`/`.down.sql`: таблица `user_avatar_objects(user_id, object_id, role)` с FK на `users` и `objects` и уникальностью `(user_id, role)`; проверить, что тесты миграций (`go test ./internal/db/...`) проходят и `make migrate` применяет и откатывает.
- [x] 1.2 Расширить `ClaimGarbage` в Postgres-сторе (`internal/photos/objects.go`): не считать мусором объекты, на которые ссылается `user_avatar_objects`; проверить тестом, что привязанный объект аватара переживает сборку, а после снятия привязки освобождается.
- [x] 1.3 Расширить `ClaimGarbage` в SQLite-сторе (`internal/photos/sqlite_objects.go`) тем же исключением; проверить аналогичным тестом на SQLite.
- [x] 1.4 Добавить методы стора для аватара (привязать/заменить/снять по ролям `avatar`/`avatar_small`, получить текущие объекты) на обоих бэкендах; проверить контрактным тестом, покрывающим оба бэкенда.
## 2. Backend: нормализация и жизненный цикл объекта аватара
- [x] 2.1 Реализовать нормализацию аватара: декодировать, отклонить не-изображение, `imaging.Fill` в 512 и 128, перекодировать в WebP (снятие EXIF/GPS); проверить юнит-тестом на JPEG с EXIF/GPS — результат квадратный 512 и 128 без EXIF.
- [x] 2.2 Реализовать сервис установки/замены/удаления аватара через журнал объектов (pending → Put → ready → привязка, откат при ошибке); проверить тестом, что сбой не оставляет объектов, а успех привязывает обе роли.
- [x] 2.3 Применить предел размера файла (10 MB) и отклонение не-изображений на границе сервиса; проверить тестами на превышение размера и на не-изображение.
## 3. Backend: API и отдача
- [x] 3.1 Добавить хендлеры `POST /api/auth/me/avatar` (multipart `file`) и `DELETE /api/auth/me/avatar`, возвращающие обновлённый self-профиль; проверить тестами: без аутентификации — отказ, после загрузки `avatar_url` задан, после удаления — пуст.
- [x] 3.2 Добавить отдачу `GET /api/avatars/:userID/:version` (малая нарезка, `?size=large` — крупная) под авторизацией; проверить тестами: без аутентификации — отказ, версия меняется при замене, отсутствующий пользователь/аватар — корректная ошибка.
- [x] 3.3 Зарегистрировать маршруты в `internal/app/app.go`; проверить тестом маршрутизации, что новые пути доступны и защищены middleware.
## 4. Аватар в представлениях пользователей
- [x] 4.1 Убедиться, что `auth/me`, участники, комментарии и автор фото отдают заполненный `avatar_url` (запросы уже выбирают колонку) и что `PublicUser`/`SelfUser` сериализуют поле; проверить тестами: у пользователя с аватаром `avatar_url` присутствует, без аватара — пуст.
## 5. Веб-интерфейс
- [x] 5.1 Добавить в `lib/api.ts` методы загрузки и удаления аватара и сохранять `avatar_url` в auth-стор; проверить `npm run check` (типы) без ошибок.
- [x] 5.2 Добавить иконку `person` в `lib/icons/registry.ts` и компонент круглого аватара с заглушкой-силуэтом; проверить, что компонент показывает изображение при заданном URL и силуэт при пустом.
- [x] 5.3 На странице `routes/(app)/profile` добавить выбор файла, канвас-кроппер и кнопки загрузки/удаления; проверить вручную: загрузка меняет аватар, удаление возвращает заглушку.
- [x] 5.4 Показывать круглый аватар в `AppShell` (шапка), у участников, в `Comments.svelte` и у автора в `PhotoPanel.svelte`; проверить визуально, что заглушка и реальный аватар отображаются круглыми.
## 6. Android-клиент
- [x] 6.1 Добавить в `ProfileApi`/`GaloApi` загрузку (multipart) и удаление аватара, а `ProfileViewModel` — `avatarUrl` и действия; проверить юнит-тестами ViewModel (успех, ошибка, обновление состояния).
- [x] 6.2 На `ProfileScreen` показать круглый аватар, выбор изображения и Compose-кроппер, кнопку удаления; проверить Compose-тестом и вручную, что аватар обновляется и сбрасывается.
- [x] 6.3 Показать круглый аватар в шапке `AlbumsScreen`; проверить Compose-тестом, что при заданном `avatarUrl` рисуется изображение, иначе — силуэт.
## 7. Интеграция
- [x] 7.1 Прогнать сквозной сценарий: загрузка аватара отражается в шапке, у участников и в комментариях; замена освобождает старый объект (проверить сборкой мусора); удаление возвращает заглушку — тест проходит на обоих бэкендах метаданных.
- [x] 7.2 Обновить документацию проекта (README/project.md), где перечислены эндпоинты и схема; проверить, что новые маршруты и таблица упомянуты.
## Workflow follow-up
- Архивировать изменение после прохождения ревью.
- Проверить результат в архивированных спеках `user-profile` и `media-objects`.