Первоначальная версия VidConf
This commit is contained in:
308
docs/api/users.md
Normal file
308
docs/api/users.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# Профиль и аватар пользователя
|
||||
|
||||
Эндпоинты управления профилем, загрузкой аватаров и поиска пользователей.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
# 1. Получить профиль
|
||||
curl http://localhost:8000/api/v1/users/me \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# 2. Обновить ФИО и команду
|
||||
curl -X PATCH http://localhost:8000/api/v1/users/me \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name_user":"Новое имя","team_id":"uuid-team"}'
|
||||
|
||||
# 3. Загрузить аватар
|
||||
curl -X POST http://localhost:8000/api/v1/users/me/avatar \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@/path/to/photo.jpg"
|
||||
|
||||
# 4. Удалить аватар
|
||||
curl -X DELETE http://localhost:8000/api/v1/users/me/avatar \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# 5. Сменить пароль
|
||||
curl -X POST http://localhost:8000/api/v1/users/me/password \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"current_password":"oldPassword123","new_password":"newSecurePassword456"}'
|
||||
|
||||
# 6. Поиск пользователей (для пикера участников)
|
||||
curl 'http://localhost:8000/api/v1/users?q=john' \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# 7. Получить справочник команд
|
||||
curl http://localhost:8000/api/v1/teams \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
## Эндпоинты
|
||||
|
||||
### GET /api/v1/users/me
|
||||
|
||||
**Получить профиль текущего аутентифицированного пользователя.**
|
||||
|
||||
**Требует auth:** JWT Bearer token
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "user@example.com",
|
||||
"name_user": "Иван Петров",
|
||||
"team_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
||||
"team_name": "Backend",
|
||||
"avatar_path": "avatars/550e8400-e29b-41d4-a716-446655440000.jpg",
|
||||
"avatar_url": "/media/avatars/550e8400-e29b-41d4-a716-446655440000.jpg",
|
||||
"created_at": "2026-07-16T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Поля:**
|
||||
- `email` — адрес электронной почты (read-only)
|
||||
- `name_user` — отображаемое имя (редактируемое)
|
||||
- `team_id` — UUID команды (опционально, редактируемое)
|
||||
- `team_name` — имя команды (null если не привязан)
|
||||
- `avatar_path` — путь к файлу аватара (null если нет)
|
||||
- `avatar_url` — готовый URL для отображения; при отсутствии аватара фронтенд показывает заглушку с инициалами
|
||||
|
||||
---
|
||||
|
||||
### PATCH /api/v1/users/me
|
||||
|
||||
**Изменить ФИО и/или команду текущего пользователя; email — read-only.**
|
||||
|
||||
**Требует auth:** JWT Bearer token
|
||||
|
||||
**Request body:** (все поля опциональны)
|
||||
```json
|
||||
{
|
||||
"name_user": "Новое имя",
|
||||
"team_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
||||
}
|
||||
```
|
||||
|
||||
**Поля:**
|
||||
- `name_user` (str, 1..255) — новое ФИО
|
||||
- `team_id` (UUID или null) — переназначить команду; явное `null` снимает привязку; отсутствие поля команду не трогает
|
||||
|
||||
**Response (200 OK):** обновленный профиль (как GET /me)
|
||||
|
||||
**Коды ошибок:**
|
||||
- `404` — team_not_found (если передан несуществующий `team_id`)
|
||||
- `422` — Unprocessable Entity (валидация: name_user пуст и т.д.)
|
||||
|
||||
---
|
||||
|
||||
### POST /api/v1/users/me/avatar
|
||||
|
||||
**Загрузить аватар текущего пользователя.**
|
||||
|
||||
**Требует auth:** JWT Bearer token
|
||||
|
||||
**Request body:** `multipart/form-data`
|
||||
- `file` — файл изображения (обязателен)
|
||||
|
||||
**Требования:**
|
||||
- **Формат:** JPEG, PNG или WebP (проверяется по magic bytes, не по расширению)
|
||||
- **Максимальный размер:** 2 МБ
|
||||
- Старый аватар автоматически удаляется при загрузке нового
|
||||
|
||||
**Response (200 OK):** обновленный профиль (как GET /me, с новым `avatar_url`)
|
||||
|
||||
**Коды ошибок:**
|
||||
- `413` — avatar_too_large (файл больше 2 МБ)
|
||||
- `415` — avatar_invalid_type (не JPEG/PNG/WebP; проверяется по magic bytes)
|
||||
- `422` — Unprocessable Entity (невалидный multipart и т.д.)
|
||||
|
||||
**Примеры:**
|
||||
```bash
|
||||
# JPEG
|
||||
curl -X POST http://localhost:8000/api/v1/users/me/avatar \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@photo.jpg"
|
||||
|
||||
# PNG
|
||||
curl -X POST http://localhost:8000/api/v1/users/me/avatar \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@photo.png"
|
||||
|
||||
# WebP
|
||||
curl -X POST http://localhost:8000/api/v1/users/me/avatar \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@photo.webp"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE /api/v1/users/me/avatar
|
||||
|
||||
**Удалить аватар текущего пользователя.**
|
||||
|
||||
**Требует auth:** JWT Bearer token
|
||||
|
||||
**Response (204 No Content)** — без тела
|
||||
|
||||
**Примечание:** После удаления `avatar_path` становится `null`, фронтенд отображает заглушку с инициалами.
|
||||
|
||||
---
|
||||
|
||||
### POST /api/v1/users/me/password
|
||||
|
||||
**Сменить пароль текущего пользователя.**
|
||||
|
||||
**Требует auth:** JWT Bearer token
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{
|
||||
"current_password": "oldPassword123",
|
||||
"new_password": "newSecurePassword456"
|
||||
}
|
||||
```
|
||||
|
||||
**Поля:**
|
||||
- `current_password` (str) — текущий пароль (обязательно, для верификации)
|
||||
- `new_password` (str, ≥8) — новый пароль (политика как при регистрации: минимум 8 символов)
|
||||
|
||||
**Response (204 No Content)** — без тела
|
||||
|
||||
**Коды ошибок:**
|
||||
- `400` — invalid_current_password (текущий пароль не совпадает)
|
||||
- `422` — Unprocessable Entity (новый пароль короче 8 символов)
|
||||
|
||||
**Примечание:** Refresh-сессии сознательно **не отзываются** (см. ADR-005 `docs/architecture/adr/005-password-reset-deferred.md`) — пользователь останется залогинен на других устройствах. Массовый отзыв всех сессий появится вместе со сбросом пароля по email (v0.1.0).
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/users
|
||||
|
||||
**Поиск пользователей для пикера участников конференции.**
|
||||
|
||||
**Требует auth:** JWT Bearer token
|
||||
|
||||
**Query параметры:**
|
||||
- `q` (str, опционально) — текст для поиска (по имени и email)
|
||||
|
||||
**Поведение:**
|
||||
- Без `q` — возвращает полный список (с ограничением ~20 результатов)
|
||||
- С `q` — полнотекстовый поиск (также ~20 результатов)
|
||||
- Поиск регистронезависим
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "ivan@example.com",
|
||||
"name_user": "Иван Петров",
|
||||
"avatar_url": "/media/avatars/550e8400-e29b-41d4-a716-446655440000.jpg"
|
||||
},
|
||||
{
|
||||
"id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
|
||||
"email": "maria@example.com",
|
||||
"name_user": "Мария Сидорова",
|
||||
"avatar_url": null
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Примеры:**
|
||||
```bash
|
||||
# Полный список
|
||||
curl http://localhost:8000/api/v1/users \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# Поиск по имени
|
||||
curl 'http://localhost:8000/api/v1/users?q=ivan' \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# Поиск по email
|
||||
curl 'http://localhost:8000/api/v1/users?q=example.com' \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Аватары
|
||||
|
||||
### Хранение
|
||||
|
||||
Аватары хранятся в каталоге `MEDIA_ROOT/avatars/`:
|
||||
|
||||
```
|
||||
MEDIA_ROOT/
|
||||
└── avatars/
|
||||
├── 550e8400-e29b-41d4-a716-446655440000.jpg
|
||||
├── 6ba7b811-9dad-11d1-80b4-00c04fd430c8.png
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Адресация:**
|
||||
- Backend сохраняет: `user.avatar_path = "avatars/{user_id}.{ext}"`
|
||||
- Frontend отображает: `avatar_url = "/media/avatars/{user_id}.{ext}"`
|
||||
- Nginx раздаёт напрямую (location `/media/` → alias `MEDIA_ROOT`)
|
||||
|
||||
### Валидация
|
||||
|
||||
**Magic bytes (не расширение):**
|
||||
- JPEG: `FF D8 FF`
|
||||
- PNG: `89 50 4E 47`
|
||||
- WebP: `52 49 46 46 ... 57 45 42 50`
|
||||
|
||||
**Размер:** max 2 МБ (413 Payload Too Large)
|
||||
|
||||
### Заглушка (дефолт)
|
||||
|
||||
Если `avatar_path` = `null`:
|
||||
- Фронтенд отображает цветную заглушку с инициалами (2 буквы ФИО)
|
||||
- Цвет выбирается детерминированно по хэшу `user_id` (всегда одинаковый для пользователя)
|
||||
|
||||
---
|
||||
|
||||
## Типы данных
|
||||
|
||||
### UserProfileOut
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "string",
|
||||
"name_user": "string",
|
||||
"team_id": "uuid or null",
|
||||
"team_name": "string or null",
|
||||
"avatar_path": "string or null",
|
||||
"avatar_url": "string or null",
|
||||
"created_at": "datetime"
|
||||
}
|
||||
```
|
||||
|
||||
### UserListItemOut
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "string",
|
||||
"name_user": "string",
|
||||
"avatar_url": "string or null"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Примечания
|
||||
|
||||
- **Email read-only:** адрес электронной почты менять нельзя (требует переверификации, которая не реализована в v0.0.1)
|
||||
- **Таймзона:** все `created_at` сохраняются в UTC (ISO 8601)
|
||||
- **Команда опциональна:** пользователь может работать без привязки к команде (`team_id` = null)
|
||||
- **Безопасность:** пароли никогда не передаются в API; для смены пароля используйте `POST /api/v1/users/me/password`. Сброс пароля по email появится в v0.1.0 (см. ADR-005)
|
||||
|
||||
---
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [Справочник команд](./teams.md) — GET /api/v1/teams
|
||||
- [Администраторский API](./admin.md) — GET/PATCH /api/v1/admin/users/{id}, POST /api/v1/admin/users/{id}/avatar
|
||||
- [Аутентификация](./auth.md) — регистрация, логин
|
||||
- [Конференции](./conferences.md) — параметр `participants` при создании/редактировании
|
||||
Reference in New Issue
Block a user