chore(openspec): archive add-user-profile and sync main specs
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-12
|
||||
@@ -0,0 +1,116 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
См. `proposal.md` — Why. Существенное для реализации:
|
||||
|
||||
- `users.email` сейчас `NOT NULL UNIQUE` в обеих схемах; уникальность — `users_email_key`.
|
||||
- Два хранилища с общим интерфейсом `auth.Repository`: `store.go` (pgx) и `sqlite_store.go`
|
||||
(`database/sql`). Две отдельные истории миграций: `backend/migrations/*.sql` (Postgres) и
|
||||
`backend/migrations/sqlite/*.sql` (консолидированная + инкременты).
|
||||
- `models.PublicUser` используется во всех публичных представлениях (участники, загрузчики,
|
||||
комментарии). `User.Public()` конвертирует в него; `/auth/me` сейчас тоже отдаёт `Public()`.
|
||||
- FK уже каскадные (`albums.owner_id`, `photos`, `comments`, `likes`, `tags`, `notifications`,
|
||||
`share_links`, `invites` → `users` ON DELETE CASCADE).
|
||||
- Блобы не в БД: удаление строк оставляет объекты, которые добирает `cmd/reconcile`.
|
||||
- `scripts/seed.py` регистрирует по email.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Единый идентификатор входа (email или E.164-телефон) без отправки SMS.
|
||||
- Управление профилем (имя, пароль) и удаление аккаунта со всем содержимым — на backend,
|
||||
web и Android.
|
||||
- Миграция без потери существующих email-only аккаунтов.
|
||||
|
||||
**Non-Goals:**
|
||||
- Верификация телефона, смена/удаление заданных логинов, avatar-загрузка, «корзина» аккаунта.
|
||||
- Мгновенная очистка блобов в транзакции удаления (остаётся на `reconcile`).
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Схема: оба идентификатора nullable, «хотя бы один» на уровне приложения
|
||||
`email` и `phone` оба `NULL`-допустимы и уникальны; обязательность «хотя бы одного»
|
||||
проверяет сервис регистрации/добавления, а не `CHECK` в БД. Так существующие строки и
|
||||
phone-only новые аккаунты сосуществуют одинаково в обеих СУБД, а сообщения об ошибке
|
||||
остаются контролируемыми. Альтернатива с `CHECK (email IS NOT NULL OR phone IS NOT NULL)`
|
||||
отвергнута: в SQLite её добавление всё равно требует rebuild, а выигрыш невелик.
|
||||
|
||||
### 2. E.164-нормализация в сервисном слое
|
||||
Отдельная функция нормализации: обязательный ведущий `+`, только цифры, длина 8–15.
|
||||
Одна точка нормализации для регистрации, добавления и входа — чтобы вход был точным
|
||||
сравнением по каноническому значению. Определение типа идентификатора: наличие `@` → email,
|
||||
иначе → телефон.
|
||||
|
||||
### 3. Добавление идентификатора — отдельный add-only эндпоинт
|
||||
`POST /auth/me/identifiers` с необязательными `email` и `phone`. Сервис разрешает ровно один
|
||||
новый идентификатор, отклоняет попытку задать уже существующий/второй, проверяет
|
||||
уникальность и конфликты (`email_taken`, `phone_taken`). Альтернатива «`PATCH /auth/me` с
|
||||
опциональными полями» отвергнута: неявное смешение изменяемого (имя) и неизменяемого
|
||||
(логины) в одном ресурсе.
|
||||
|
||||
### 4. Смена пароля: текущий пароль + отзыв всех refresh-токенов
|
||||
`POST /auth/me/password { current_password, new_password }`. При успехе `RevokeAllUserTokens`.
|
||||
Клиенты очищают токены и уходят на экран входа. Access-токен (≤15 мин) формально жив, но
|
||||
refresh мёртв; для консистентности клиенты всё равно разлогиниваются.
|
||||
|
||||
### 5. Удаление аккаунта: подтверждение паролем + удаление строки
|
||||
`DELETE /auth/me { password }`. Проверяем пароль, затем `DELETE FROM users WHERE id`.
|
||||
Каскад FK удаляет альбомы (вместе с доступом других участников), фото, соцданные,
|
||||
уведомления, share-ссылки, приглашения и токены. После удаления любой запрос с прежним
|
||||
access-токеном завершается 401, потому что строки пользователя больше нет. Возврат 204.
|
||||
Блобы освобождаются `reconcile`. Альтернативы (запрет при общих альбомах, передача
|
||||
владения, grace-период) отклонены по решению: удалять всё и сразу.
|
||||
|
||||
### 6. Приватность телефона
|
||||
`phone` добавляется в `models.User`, но НЕ в `PublicUser`, поэтому не попадает в списки
|
||||
участников/загрузчиков/комментариев. Собственный профиль отдаётся отдельным ответом с
|
||||
телефоном, а не через `Public()`.
|
||||
|
||||
### 7. Миграции
|
||||
- Postgres `000013_user_phone`: `ALTER TABLE users ALTER COLUMN email DROP NOT NULL`,
|
||||
`ADD COLUMN phone text`, `ADD CONSTRAINT users_phone_key UNIQUE (phone)`.
|
||||
- SQLite `000005_user_phone`: rebuild `users` по 12-шаговому паттерну (создать новую таблицу
|
||||
без `NOT NULL` у email, с `phone`, перенести данные, пересоздать индексы), т.к. SQLite
|
||||
не умеет снимать ограничения через `ALTER`.
|
||||
- Существующие строки получают `phone = NULL`; email сохраняется.
|
||||
|
||||
### 8. Клиенты
|
||||
- Web: маршрут `/profile` под `(app)`, вход из шапки по имени; login-страница — одно поле
|
||||
«Email или телефон»; при регистрации поля email/phone с проверкой «хотя бы одно».
|
||||
- Android: `ProfileScreen` в `GaloNavHost`, вход из `AlbumsScreen`; `AuthScreen` — одно поле
|
||||
идентификатора и необязательные email/phone при регистрации; по успеху пароль/удаление →
|
||||
очистка `TokenStore` → на экран auth.
|
||||
- Оба клиента обновляются вместе с сервером: контракт login/register — **BREAKING**.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Телефон-логин без верификации → занятие чужого номера] → принято осознанно для
|
||||
self-hosted; задел под OTP вне объёма.
|
||||
- [SQLite rebuild `users`] → выполнять в одной миграции с временным отключением FK и
|
||||
пересозданием индексов; проверить на копии `galo.db`.
|
||||
- [Запросы сканируют `email` в `string`; теперь возможен NULL] → перейти на
|
||||
`COALESCE(email,'')` в обоих хранилищах и покрыть тестами.
|
||||
- [Гонка уникальности] → опираться на unique-индексы, маппить нарушение в `email_taken`/
|
||||
`phone_taken`.
|
||||
- [Удаление каскадом уничтожает общие альбомы] → необратимо и покрыто подтверждением
|
||||
паролем; поведение задокументировано в спеке.
|
||||
- [Access-токен живёт ≤15 мин после смены пароля/удаления] → refresh отозван, при удалении
|
||||
пользователь отсутствует; клиенты разлогиниваются немедленно.
|
||||
- [Значение email в JWT-claim может быть пустым] → claim не обязателен для авторизации
|
||||
(используется `uid`); оставляем как есть.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Применить миграции: Postgres `000013`, SQLite `000005` (на копии проверить rebuild).
|
||||
2. Задеплоить backend с новым контрактом.
|
||||
3. Обновить web и Android синхронно (единый релиз) из-за breaking login/register.
|
||||
4. Проверки: регистрация email-only, phone-only, обоими; вход обоими; добавление второго
|
||||
идентификатора; смена пароля (все сессии отозваны); удаление аккаунта с общими альбомами.
|
||||
5. Откат: down-миграции возвращают `email NOT NULL` и удаляют `phone`; выполним только если
|
||||
нет phone-only аккаунтов. Удаление аккаунтов необратимо.
|
||||
|
||||
## Open Questions
|
||||
|
||||
Нет — все развилки (верификация, обязательность, иммутабельность, политика пароля,
|
||||
поведение общих альбомов, подтверждение удаления) зафиксированы в `proposal.md` и спеках.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
Управление аккаунтом сейчас ограничено регистрацией и входом: пользователь не может
|
||||
изменить имя или пароль, а `PATCH /auth/me/preferences` умеет только настройки отображения.
|
||||
Вход возможен только по email, хотя мобильный сценарий естественно подразумевает номер
|
||||
телефона. Нужен явный профиль и вход по любому из двух идентификаторов.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Профиль пользователя: изменение имени; смена пароля с подтверждением текущего пароля и
|
||||
отзывом **всех** refresh-токенов (все сессии, включая текущую).
|
||||
- `phone` добавляется в аккаунт как второй идентификатор входа, хранится в строгом формате
|
||||
E.164, уникален, без подтверждения (SMS/OTP вне объёма).
|
||||
- `email` перестаёт быть обязательным: обязателен **хотя бы один** из `email`/`phone`.
|
||||
Оба — логины: заданный идентификатор нельзя изменить или удалить, но недостающий можно
|
||||
добавить один раз (add-only).
|
||||
- Вход по единому полю: `identifier` — email или E.164-телефон; определение по наличию `@`.
|
||||
- **BREAKING** (API): `POST /auth/login` принимает `identifier` вместо `email`;
|
||||
`POST /auth/register` принимает `phone` и допускает отсутствие `email`.
|
||||
- Новые инструменты профиля на всех поверхностях: страница `/profile` в вебе и отдельный
|
||||
экран в Android; поля входа/регистрации обновляются на обеих поверхностях.
|
||||
- Удаление аккаунта со всем содержимым: по подтверждению текущего пароля, немедленно и
|
||||
необратимо. Каскадно удаляются альбомы пользователя (включая доступ других участников),
|
||||
фото, комментарии, лайки, метки, уведомления, share-ссылки и приглашения; блобы добираются
|
||||
фоновым `reconcile`.
|
||||
- Вне объёма: подтверждение телефона (SMS/OTP), смена/удаление заданных логинов, изменение
|
||||
avatar, «корзина»/отложенное удаление аккаунта, экспорт данных перед удалением.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `user-profile`: управление аккаунтом пользователя на всех поверхностях — чтение профиля,
|
||||
изменение имени, смена пароля с отзывом сессий, добавление недостающего идентификатора и
|
||||
удаление аккаунта со всем содержимым; телефон виден только владельцу (email, как и прежде,
|
||||
виден участникам альбома).
|
||||
|
||||
### Modified Capabilities
|
||||
- `auth`: вход по email **или** телефону через единый `identifier`; регистрация с
|
||||
требованием «хотя бы одного» из email/phone; хранение телефона в E.164.
|
||||
|
||||
## Impact
|
||||
|
||||
- Backend: `backend/internal/auth/{service.go,handlers.go,store.go,sqlite_store.go,repository.go}`,
|
||||
`backend/internal/models/models.go`, `backend/internal/app/app.go` (маршруты).
|
||||
- Миграции: PostgreSQL `000013_user_phone.*`, SQLite `000005_user_phone.*` (в SQLite — rebuild
|
||||
`users` для снятия `NOT NULL` с email).
|
||||
- Frontend: `routes/(public)/login/+page.svelte`, новая `routes/(app)/profile/+page.svelte`,
|
||||
`lib/components/AppShell.svelte`, `lib/api.ts`, `lib/types.ts`, `lib/stores/auth.svelte.ts`.
|
||||
- Android: `data/network/dto/Dtos.kt`, `data/network/GaloApi.kt`, `data/network/ErrorMapper.kt`,
|
||||
`ui/auth/*`, новый `ui/profile/*`, `ui/GaloNavHost.kt`.
|
||||
- Данные/скрипты: `scripts/seed.py` (передача phone), существующие аккаунты остаются
|
||||
email-only и получают `phone = NULL`.
|
||||
- Удаление: каскад FK в PostgreSQL/SQLite; очистка блобов существующим `reconcile`
|
||||
(eventual, не в транзакции удаления).
|
||||
- Приватность: `phone` не попадает в `PublicUser` (участники, загрузчики, комментарии).
|
||||
@@ -0,0 +1,41 @@
|
||||
# Spec Delta
|
||||
|
||||
## RENAMED Requirements
|
||||
- FROM: `### Requirement: Вход по email и паролю`
|
||||
- TO: `### Requirement: Вход по email или телефону`
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Регистрация пользователя
|
||||
Пользователь SHALL регистрироваться, указав имя, пароль и хотя бы один из идентификаторов —
|
||||
email или телефон. Пароль SHALL храниться в виде bcrypt-хэша. Телефон SHALL нормализоваться
|
||||
к формату E.164 и быть уникальным; email SHALL приводиться к нижнему регистру и быть
|
||||
уникальным.
|
||||
|
||||
#### Scenario: Успешная регистрация
|
||||
- **WHEN** пользователь отправляет свободный email, имя и пароль
|
||||
- **THEN** система создаёт учётную запись и возвращает access- и refresh-токены
|
||||
|
||||
#### Scenario: Регистрация по телефону
|
||||
- **WHEN** пользователь отправляет свободный телефон в формате E.164, имя и пароль
|
||||
- **THEN** система создаёт учётную запись и возвращает access- и refresh-токены
|
||||
|
||||
#### Scenario: Нет ни email, ни телефона
|
||||
- **WHEN** пользователь не указывает ни email, ни телефон
|
||||
- **THEN** система отклоняет регистрацию
|
||||
|
||||
#### Scenario: Повторный email
|
||||
- **WHEN** пользователь регистрируется с уже занятым email или телефоном
|
||||
- **THEN** система возвращает ошибку конфликта и не создаёт вторую запись
|
||||
|
||||
### Requirement: Вход по email или телефону
|
||||
Пользователь SHALL входить по единому идентификатору — email или телефону в формате
|
||||
E.164 — и паролю.
|
||||
|
||||
#### Scenario: Успешный вход
|
||||
- **WHEN** пользователь вводит существующий email или телефон и верный пароль
|
||||
- **THEN** система возвращает access- и refresh-токены
|
||||
|
||||
#### Scenario: Неверные учётные данные
|
||||
- **WHEN** пользователь вводит несуществующий идентификатор или неверный пароль
|
||||
- **THEN** система возвращает 401, не раскрывая, какое именно поле неверно
|
||||
@@ -0,0 +1,120 @@
|
||||
# Spec Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
Управление аккаунтом пользователя: просмотр и изменение профиля, смена пароля с отзывом
|
||||
сессий, добавление второго идентификатора входа и удаление аккаунта со всем содержимым —
|
||||
единообразно в веб-интерфейсе и Android-клиенте.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Просмотр собственного профиля
|
||||
Пользователь SHALL получать свой профиль с именем, email, телефоном и аватаром.
|
||||
Телефон SHALL быть виден только владельцу и SHALL NOT попадать в публичные представления
|
||||
пользователя (участники альбома, загрузчики, комментарии).
|
||||
|
||||
#### Scenario: Получение профиля
|
||||
- **WHEN** аутентифицированный пользователь запрашивает свой профиль
|
||||
- **THEN** ответ содержит его имя, email (если задан), телефон (если задан) и аватар
|
||||
|
||||
#### Scenario: Телефон не утекает
|
||||
- **WHEN** пользователь просматривает участников альбома
|
||||
- **THEN** представления других пользователей не содержат телефон
|
||||
|
||||
### Requirement: Изменение имени
|
||||
Пользователь SHALL изменять своё имя. Пустое имя SHALL отклоняться. Смена имени SHALL
|
||||
применяться немедленно и отражаться при следующем чтении профиля.
|
||||
|
||||
#### Scenario: Успешное изменение
|
||||
- **WHEN** пользователь отправляет непустое имя
|
||||
- **THEN** система сохраняет его и возвращает обновлённый профиль
|
||||
|
||||
#### Scenario: Пустое имя
|
||||
- **WHEN** пользователь отправляет пустое имя
|
||||
- **THEN** система отклоняет запрос и не меняет профиль
|
||||
|
||||
### Requirement: Смена пароля с отзывом сессий
|
||||
Пользователь SHALL менять пароль, подтвердив текущий пароль. Система SHALL отклонять
|
||||
неверный текущий пароль и новый пароль короче 8 символов. Успешная смена SHALL отзывать
|
||||
все refresh-токены пользователя на всех устройствах.
|
||||
|
||||
#### Scenario: Успешная смена
|
||||
- **WHEN** пользователь вводит верный текущий пароль и новый пароль допустимой длины
|
||||
- **THEN** система сохраняет новый хэш и отзывает все refresh-токены пользователя
|
||||
|
||||
#### Scenario: Неверный текущий пароль
|
||||
- **WHEN** пользователь вводит неверный текущий пароль
|
||||
- **THEN** система возвращает ошибку и не меняет пароль
|
||||
|
||||
#### Scenario: Слабый новый пароль
|
||||
- **WHEN** новый пароль короче 8 символов
|
||||
- **THEN** система отклоняет запрос и сохраняет старый пароль
|
||||
|
||||
### Requirement: Добавление недостающего идентификатора
|
||||
Пользователь с одним заданным идентификатором (email или телефон) SHALL добавить
|
||||
недостающий один раз после проверки уникальности. Система SHALL отклонять добавление, когда
|
||||
оба идентификатора уже заданы, а также изменение или удаление заданного. После добавления
|
||||
оба идентификатора SHALL работать для входа.
|
||||
|
||||
#### Scenario: Добавление телефона
|
||||
- **WHEN** пользователь без телефона добавляет свободный номер в формате E.164
|
||||
- **THEN** система сохраняет номер, и вход по нему становится возможен
|
||||
|
||||
#### Scenario: Занятый идентификатор
|
||||
- **WHEN** добавляемый email или телефон уже принадлежит другому пользователю
|
||||
- **THEN** система возвращает ошибку конфликта и не меняет профиль
|
||||
|
||||
#### Scenario: Оба уже заданы
|
||||
- **WHEN** у пользователя уже заданы и email, и телефон, и он пытается добавить третий
|
||||
- **THEN** система отклоняет запрос
|
||||
|
||||
### Requirement: Удаление аккаунта со всем содержимым
|
||||
Пользователь SHALL удалять свой аккаунт, подтвердив текущий пароль. Неверный пароль SHALL
|
||||
отклоняться. Успешное удаление SHALL быть немедленным и необратимым: SHALL каскадно
|
||||
удаляться альбомы пользователя (вместе с доступом других участников), его фото,
|
||||
комментарии, лайки, метки, уведомления, share-ссылки и приглашения. Блобы SHALL
|
||||
освобождаться фоновой сверкой хранилища, а не в транзакции удаления.
|
||||
|
||||
#### Scenario: Успешное удаление
|
||||
- **WHEN** пользователь подтверждает удаление верным текущим паролем
|
||||
- **THEN** учётная запись и связанное содержимое удаляются, и вход больше невозможен
|
||||
|
||||
#### Scenario: Общие альбомы
|
||||
- **WHEN** удаляемый пользователь владеет альбомом, в котором есть другие участники
|
||||
- **THEN** альбом удаляется вместе с ними
|
||||
|
||||
#### Scenario: Неверный пароль
|
||||
- **WHEN** пользователь подтверждает удаление неверным паролем
|
||||
- **THEN** система возвращает ошибку и не удаляет аккаунт
|
||||
|
||||
### Requirement: Страница профиля в веб-интерфейсе
|
||||
Веб-приложение SHALL предоставлять отдельную страницу профиля, доступную из шапки, с
|
||||
показом идентификаторов и формами изменения имени, смены пароля и удаления аккаунта.
|
||||
|
||||
#### Scenario: Открытие страницы
|
||||
- **WHEN** аутентифицированный пользователь открывает профиль из шапки
|
||||
- **THEN** отображаются его идентификаторы и формы редактирования
|
||||
|
||||
#### Scenario: Выход после смены пароля
|
||||
- **WHEN** смена пароля завершилась успешно
|
||||
- **THEN** клиент очищает токены и перенаправляет на страницу входа
|
||||
|
||||
#### Scenario: Выход после удаления
|
||||
- **WHEN** удаление аккаунта завершилось успешно
|
||||
- **THEN** клиент очищает токены и перенаправляет на страницу входа
|
||||
|
||||
### Requirement: Экран профиля в Android-клиенте
|
||||
Android-приложение SHALL предоставлять отдельный экран профиля, доступный из списка
|
||||
альбомов, с теми же возможностями, что и веб-страница.
|
||||
|
||||
#### Scenario: Открытие экрана
|
||||
- **WHEN** пользователь открывает профиль из списка альбомов
|
||||
- **THEN** отображаются идентификаторы и формы редактирования
|
||||
|
||||
#### Scenario: Возврат после смены пароля
|
||||
- **WHEN** смена пароля завершилась успешно
|
||||
- **THEN** приложение очищает токены и возвращает на экран входа
|
||||
|
||||
#### Scenario: Возврат после удаления
|
||||
- **WHEN** удаление аккаунта завершилось успешно
|
||||
- **THEN** приложение очищает токены и возвращает на экран входа
|
||||
@@ -0,0 +1,51 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Модель данных и миграции
|
||||
|
||||
- [x] 1.1 Добавить `Phone` в `models.User` (но НЕ в `PublicUser`) — unit-тест: JSON `PublicUser` не содержит телефон
|
||||
- [x] 1.2 Миграция PostgreSQL `000013_user_phone` (снять `NOT NULL` с email, добавить `phone` + unique, down-миграция) — проверить `make migrate` и откат на чистой БД
|
||||
- [x] 1.3 Миграция SQLite `000005_user_phone` через rebuild `users` — проверить на копии `galo.db`, что данные сохранены, email nullable, `phone` присутствует
|
||||
- [x] 1.4 Расширить миграционный тест `internal/e2e` — проверка схемы `users` (nullable email, колонка `phone`, уникальность)
|
||||
|
||||
## 2. Backend: вход и регистрация по идентификатору
|
||||
|
||||
- [x] 2.1 Нормализация E.164 и определение типа идентификатора (`@` → email) — unit-тест на валидные/невалидные номера
|
||||
- [x] 2.2 Хранилища: `COALESCE(email,'')` в выборках, `GetUserByPhone` в `store.go` и `sqlite_store.go` — тесты обоих хранилищ
|
||||
- [x] 2.3 `Service.Register` принимает email и/или телефон, требует «хотя бы один», ловит конфликты `email_taken`/`phone_taken` — тесты
|
||||
- [x] 2.4 `Service.Login` по единому идентификатору — тесты: вход по email, по телефону, несуществующий, неверный пароль (ответ неразличим)
|
||||
- [x] 2.5 Хендлеры `login` (`identifier`) и `register` (`phone` необязателен) — тесты хендлеров
|
||||
|
||||
## 3. Backend: профиль (имя, пароль, добавление идентификатора)
|
||||
|
||||
- [x] 3.1 `PATCH /auth/me` для изменения имени (пустое отклоняется) — тесты сервиса и хендлера
|
||||
- [x] 3.2 `POST /auth/me/password`: проверка текущего пароля, минимум 8 символов, `RevokeAllUserTokens` при успехе — тесты
|
||||
- [x] 3.3 `POST /auth/me/identifiers` (add-only): добавление недостающего email/phone, конфликты, отказ когда оба заданы — тесты
|
||||
- [x] 3.4 `/auth/me` отдаёт телефон только владельцу — тест: ответ `Me` содержит phone, публичные представления — нет
|
||||
|
||||
## 4. Backend: удаление аккаунта
|
||||
|
||||
- [x] 4.1 `DELETE /auth/me` с подтверждением пароля и `DeleteUser` в обоих хранилищах — тесты: неверный пароль, успешное удаление
|
||||
- [x] 4.2 Интеграционный тест: удаление владельца с общим альбомом убирает альбом у другого участника
|
||||
|
||||
## 5. Web-интерфейс
|
||||
|
||||
- [x] 5.1 Login/register: единое поле «Email или телефон», необязательные email/phone с проверкой «хотя бы одно»; обновить `api.ts`, `types.ts` — проверить `npm run check` и ручной вход
|
||||
- [x] 5.2 Новая страница `(app)/profile` (имя, пароль, добавление идентификатора, удаление) и ссылка из шапки `AppShell` — проверить `npm run check` и ручной сценарий
|
||||
- [x] 5.3 Очистка токенов и редирект на вход после смены пароля и удаления — ручная проверка в браузере
|
||||
|
||||
## 6. Android-клиент
|
||||
|
||||
- [x] 6.1 DTO/API: login по `identifier`, `phone` при регистрации, эндпоинты профиля; обновить `ErrorMapper` — unit-тест маппинга ошибок
|
||||
- [x] 6.2 `AuthScreen`: единое поле идентификатора, необязательные email/phone — Robolectric-тест
|
||||
- [x] 6.3 `ProfileScreen` + `ProfileViewModel` + запись в `GaloNavHost`, вход из списка альбомов — Robolectric-тест
|
||||
- [x] 6.4 Очистка `TokenStore` и возврат на экран auth после смены пароля/удаления — Robolectric-тест
|
||||
|
||||
## 7. Интеграция и документация
|
||||
|
||||
- [x] 7.1 E2E против запущенного сервера: регистрация только по телефону, вход, добавление email, смена пароля (сессии отозваны), удаление аккаунта
|
||||
- [x] 7.2 `scripts/seed.py`: поддержать необязательный `phone`; обновить README (новые эндпоинты профиля) — проверить запуск `make testdata` и чтение README
|
||||
- [ ] 7.3 Полный прогон: `make test`, `cd frontend && npm run check`, `cd android && ./gradlew test`
|
||||
|
||||
## Workflow follow-up
|
||||
|
||||
- Архивировать изменение после прохождения ревью и синхронизировать основные спеки.
|
||||
Reference in New Issue
Block a user