API Модули — Полный код¶
Уровень: L3 (deep-dive) | ← Назад к API Layer
1. Auth API¶
Файл: frontend/src/api/auth.ts
Авторизация, регистрация, управление профилем.
import { apiClient } from './client';
import type {
ChangePasswordRequest,
LoginRequest,
RefreshRequest,
RegisterRequest,
TokenResponse,
User,
UserUpdate,
} from '../types/api';
export const authApi = {
// Вход по логину и паролю → пара токенов
login: (data: LoginRequest) =>
apiClient.post<TokenResponse>('/auth/login', data).then((r) => r.data),
// Регистрация нового пользователя → пара токенов
register: (data: RegisterRequest) =>
apiClient.post<TokenResponse>('/auth/register', data).then((r) => r.data),
// Обновление access token по refresh token
refresh: (refreshToken: string) =>
apiClient
.post<TokenResponse>('/auth/refresh', { refresh_token: refreshToken } satisfies RefreshRequest)
.then((r) => r.data),
// Получить текущего пользователя
getMe: () =>
apiClient.get<User>('/users/me').then((r) => r.data),
// Обновить профиль (имя, аватар)
updateMe: (data: UserUpdate) =>
apiClient.patch<User>('/users/me', data).then((r) => r.data),
// Смена пароля
changePassword: (data: ChangePasswordRequest) =>
apiClient.post<void>('/users/me/password', data).then(() => undefined),
};
Типы запросов/ответов¶
interface LoginRequest { login: string; password: string; }
interface RegisterRequest { name: string; email: string; phone?: string; password: string; }
interface RefreshRequest { refresh_token: string; }
interface TokenResponse { access_token: string; refresh_token: string; expires_in: number; }
interface User { id: string; name: string; email: string; phone?: string; avatar_media_id?: string; created_at: string; }
interface UserUpdate { name?: string; avatar_media_id?: string; }
interface ChangePasswordRequest { current_password: string; new_password: string; }
2. Items API¶
Файл: frontend/src/api/items.ts
CRUD операции с вещами гардероба.
import { apiClient } from './client';
import type { Item, ItemCreate, ItemUpdate, ItemList, ItemListParams } from '../types/api';
export const itemsApi = {
// Список вещей с фильтрацией и пагинацией
list: (params?: ItemListParams) =>
apiClient.get<ItemList>('/items', { params }).then((r) => r.data),
// Получить одну вещь по ID
get: (id: string) =>
apiClient.get<Item>(`/items/${id}`).then((r) => r.data),
// Создать новую вещь
create: (data: ItemCreate) =>
apiClient.post<Item>('/items', data).then((r) => r.data),
// Обновить вещь (partial update)
update: (id: string, data: ItemUpdate) =>
apiClient.patch<Item>(`/items/${id}`, data).then((r) => r.data),
// Удалить вещь
delete: (id: string) =>
apiClient.delete(`/items/${id}`).then((r) => r.data),
};
Типы¶
interface ItemListParams {
category_id?: number; color_id?: number; season?: Season;
style_node_id?: number; slot_id?: number; status?: ItemStatus;
include_embeddings?: boolean; limit?: number; offset?: number;
}
interface ItemCreate { title?: string; category_id: number; primary_color_id?: number; primary_media_id?: string; style_node_ids?: number[]; }
interface ItemUpdate { title?: string; category_id?: number; primary_color_id?: number; primary_media_id?: string; status?: ItemStatus; excluded_from_recommendations?: boolean; style_node_ids?: number[]; }
interface ItemList { items: Item[]; total: number; limit: number; offset: number; }
3. Outfits API¶
Файл: frontend/src/api/outfits.ts
CRUD образов, сохранение, генерация названий, шеринг.
import { apiClient } from './client';
import type {
Outfit, OutfitCreate, OutfitSuggestTitleRequest, OutfitSuggestTitleResponse,
OutfitUpdate, OutfitList, OutfitListParams, ShareLink, ShareFormat,
} from '../types/api';
export const outfitsApi = {
// Список образов с фильтрацией
list: (params?: OutfitListParams) =>
apiClient.get<OutfitList>('/outfits', { params }).then((r) => r.data),
// Получить один образ по ID
get: (id: string) =>
apiClient.get<Outfit>(`/outfits/${id}`).then((r) => r.data),
// Создать новый образ
create: (data: OutfitCreate) =>
apiClient.post<Outfit>('/outfits', data).then((r) => r.data),
// Предложить название для образа (ML на бэкенде)
suggestTitle: (data: OutfitSuggestTitleRequest) =>
apiClient.post<OutfitSuggestTitleResponse>('/outfits/suggest-title', data).then((r) => r.data),
// Обновить образ
update: (id: string, data: OutfitUpdate) =>
apiClient.patch<Outfit>(`/outfits/${id}`, data).then((r) => r.data),
// Удалить образ
delete: (id: string) =>
apiClient.delete(`/outfits/${id}`).then((r) => r.data),
// Сохранить чужой образ в коллекцию
save: (id: string) =>
apiClient.post(`/outfits/${id}/save`).then((r) => r.data),
// Убрать из сохранённых
unsave: (id: string) =>
apiClient.delete(`/outfits/${id}/save`).then((r) => r.data),
// Получить сохранённые образы
saved: (params?: { origin_type?: 'own' | 'external'; limit?: number; offset?: number }) =>
apiClient.get<OutfitList>('/outfits/saved', { params }).then((r) => r.data),
// Получить похожие образы (серверный поиск по слотам)
similar: (id: string, params?: { slot_id?: number; limit?: number; offset?: number }) =>
apiClient.get<OutfitList>(`/outfits/${id}/similar`, { params }).then((r) => r.data),
// Создать публичную ссылку для шеринга
share: (id: string, format: ShareFormat) =>
apiClient.post<ShareLink>(`/outfits/${id}/share`, { format }).then((r) => r.data),
};
4. Feed API¶
Файл: frontend/src/api/feed.ts
Лента образов с фильтрацией по погоде, стилю, цвету.
import { apiClient } from './client';
import type { FeedList, FeedParams } from '../types/api';
export const feedApi = {
// Получить ленту образов с фильтрами
list: (params?: FeedParams) =>
apiClient.get<FeedList>('/feed', { params }).then((r) => r.data),
};
Типы фильтров ленты¶
interface FeedParams {
origin_type?: 'own' | 'external';
occasion?: string; style?: string; weather?: string;
color_id?: number; season?: Season; category_id?: number;
slot_id?: number; collection?: string;
limit?: number; offset?: number; cursor?: number;
}
interface FeedList extends OutfitList { next_offset?: number; }
5. Media API¶
Файл: frontend/src/api/media.ts
Загрузка медиафайлов с автоматической компрессией.
import { apiClient } from './client';
import type { Media, MediaKind, MediaUploadResponse } from '../types/api';
import { compressImage } from '../utils/compressImage';
// Форматирование размера файла для логов
function formatFileSize(bytes: number): string {
if (bytes < 1024 * 1024) {
return `${Math.max(1, Math.round(bytes / 1024))}KB`;
}
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
}
export const mediaApi = {
// Загрузка файла с компрессией в зависимости от типа
upload: async (file: File, kind: MediaKind = 'other') => {
// Профили компрессии для разных типов медиа
const compressOpts =
kind === 'item_photo'
? { maxWidth: 896, maxHeight: 896, quality: 0.8, format: 'jpeg' as const }
: kind === 'avatar'
? { maxWidth: 512, maxHeight: 512, quality: 0.85, format: 'jpeg' as const }
: null; // Для остальных типов — без компрессии
// Применить компрессию если указан профиль
const uploadFile = compressOpts ? await compressImage(file, compressOpts) : file;
// Логирование результата компрессии
if (kind === 'item_photo' || kind === 'avatar') {
console.info(
`[media] compress: ${formatFileSize(file.size)} => ${formatFileSize(uploadFile.size)} (${uploadFile.type})`
);
}
// Отправка как multipart/form-data
const formData = new FormData();
formData.append('file', uploadFile);
formData.append('kind', kind);
return apiClient
.post<MediaUploadResponse>('/media', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then((r) => r.data);
},
// Получить метаданные медиа по ID
get: (id: string) =>
apiClient.get<Media>(`/media/${id}`).then((r) => r.data),
};
Профили компрессии¶
| Тип | Размер | Quality | Формат |
|---|---|---|---|
item_photo |
896×896 | 0.8 | JPEG |
avatar |
512×512 | 0.85 | JPEG |
| Остальные | Без изменений | — | — |
6. Share API¶
Файл: frontend/src/api/share.ts
Публичные ссылки на образы, импорт чужих образов.
import { apiClient } from './client';
import type { Outfit, SharedOutfitList } from '../types/api';
export const shareApi = {
// Получить образ по публичному токену (без авторизации)
getByToken: (token: string) =>
apiClient.get<Outfit>(`/share/${token}`).then((r) => r.data),
// Импортировать (claim) чужой образ к себе
claim: (token: string) =>
apiClient.post<Outfit>(`/share/${token}`).then((r) => r.data),
// Получить свои расшаренные образы
getMySharedOutfits: (params?: { limit?: number; offset?: number }) =>
apiClient
.get<SharedOutfitList>('/users/me/shared-outfits', { params })
.then((r) => r.data),
// Отозвать публичную ссылку
revoke: (token: string) =>
apiClient.delete(`/share/${token}`).then((r) => r.data),
};
7. Reference API¶
Файл: frontend/src/api/reference.ts
Справочные данные (кешируются в localStorage для offline).
import { apiClient } from './client';
import type { Color, ItemCategory, Slot, StyleNode, Collection, StyleNodeKind } from '../types/api';
export const referenceApi = {
// Все цвета
colors: () =>
apiClient.get<Color[]>('/reference/colors').then((r) => r.data),
// Категории вещей (с фильтрацией по parent_id или slot_id)
categories: (params?: { parent_id?: number; slot_id?: number }) =>
apiClient.get<ItemCategory[]>('/reference/categories', { params }).then((r) => r.data),
// Слоты (верх, низ, обувь и т.д.)
slots: () =>
apiClient.get<Slot[]>('/reference/slots').then((r) => r.data),
// Стили (паттерн, эстетика, фит, сезон)
styles: (params?: { kind?: StyleNodeKind; parent_id?: number }) =>
apiClient.get<StyleNode[]>('/reference/styles', { params }).then((r) => r.data),
// Коллекции (тематические подборки)
collections: () =>
apiClient.get<Collection[]>('/reference/collections').then((r) => r.data),
};
Типы справочников¶
interface Color { id: number; name: string; }
interface ItemCategory { id: number; name: string; parent_id?: number; default_slot_id?: number; }
interface Slot { id: number; code: string; name: string; order_index: number; min_layers: number; max_layers: number; }
type StyleNodeKind = 'pattern' | 'aesthetic' | 'fit' | 'season' | 'other';
interface StyleNode { id: number; name: string; kind: StyleNodeKind; parent_id?: number; }
interface Collection { code: string; name: string; description?: string; }