81 lines
4.0 KiB
TypeScript
81 lines
4.0 KiB
TypeScript
/**
|
||
* API-функции профиля текущего пользователя и поиска зарегистрированных
|
||
* пользователей (участники конференций, профиль).
|
||
*/
|
||
import { apiRequest } from '@/api/client'
|
||
import type { CurrentUser } from '@/api/auth'
|
||
|
||
/** Элемент результата поиска пользователей (пикер участников конференции) — без email, только для отображения/выбора по `id`. */
|
||
export interface UserSearchResult {
|
||
id: string
|
||
display_name: string
|
||
avatar_url: string | null
|
||
}
|
||
|
||
/** Поиск зарегистрированных пользователей по имени/email — для пикера участников конференции. */
|
||
export async function searchUsers(query: string, limit = 8): Promise<UserSearchResult[]> {
|
||
const params = new URLSearchParams({ q: query, limit: String(limit) })
|
||
return apiRequest<UserSearchResult[]>(`/users?${params.toString()}`)
|
||
}
|
||
|
||
/** Команда в справочнике команд (страница профиля). */
|
||
export interface TeamOption {
|
||
id: string
|
||
name: string
|
||
}
|
||
|
||
/**
|
||
* Справочник команд для страницы профиля — аутентифицированный эндпоинт,
|
||
* не зависит от тумблера `registration_team_choice` (в отличие от
|
||
* `getRegistrationOptions` в `api/auth.ts`, который гасит список команд при
|
||
* выключенном выборе команды на регистрации).
|
||
*/
|
||
export async function listTeams(): Promise<TeamOption[]> {
|
||
return apiRequest<TeamOption[]>('/teams')
|
||
}
|
||
|
||
/** Тело правки собственного профиля — ФИО и/или команда, email не редактируется. */
|
||
export interface UpdateMePayload {
|
||
name_user?: string
|
||
/** `null` — снять с команды. */
|
||
team_id?: string | null
|
||
}
|
||
|
||
/** Частично обновить собственный профиль. */
|
||
export async function updateMe(payload: UpdateMePayload): Promise<CurrentUser> {
|
||
return apiRequest<CurrentUser>('/users/me', { method: 'PATCH', body: payload })
|
||
}
|
||
|
||
/**
|
||
* Загрузить аватар текущего пользователя. 200 — полный обновлённый профиль.
|
||
* 413 — файл больше 2 МБ, 415 — недопустимый тип (разрешены jpeg/png/webp);
|
||
* оба кода обрабатываются вызывающей стороной через `ApiError.status`.
|
||
*/
|
||
export async function uploadMyAvatar(file: File): Promise<CurrentUser> {
|
||
const form = new FormData()
|
||
form.append('file', file)
|
||
return apiRequest<CurrentUser>('/users/me/avatar', { method: 'POST', body: form })
|
||
}
|
||
|
||
/** Удалить аватар текущего пользователя — 204 без тела; вызывающая сторона сама перечитывает профиль (см. `refreshUser`). */
|
||
export async function deleteMyAvatar(): Promise<void> {
|
||
await apiRequest('/users/me/avatar', { method: 'DELETE' })
|
||
}
|
||
|
||
/** Тело смены пароля текущего пользователя. */
|
||
export interface PasswordChangePayload {
|
||
current_password: string
|
||
new_password: string
|
||
}
|
||
|
||
/**
|
||
* Сменить пароль текущего пользователя. 204 без тела. 400 `invalid_current_password` —
|
||
* неверный текущий пароль, 422 — новый пароль короче 8 символов.
|
||
*
|
||
* Refresh-сессии сознательно не отзываются backend'ом (минимальный дифф —
|
||
* см. ADR-005, отзыв запланирован вместе со сбросом пароля по email в v0.1.0).
|
||
*/
|
||
export async function changePassword(payload: PasswordChangePayload): Promise<void> {
|
||
await apiRequest('/users/me/password', { method: 'POST', body: payload })
|
||
}
|