changes for md
This commit is contained in:
726
docs/API_DOCS_RU.md
Normal file
726
docs/API_DOCS_RU.md
Normal file
@@ -0,0 +1,726 @@
|
||||
# Полная документация Backend API
|
||||
|
||||
> **Последнее обновление:** Февраль 2026
|
||||
> **Фронтенд:** Angular 21 · Два бренда (Dexar + Novo)
|
||||
> **Охватывает:** Каталог, Корзина, Оплата, Отзывы, Регионы, Авторизация, i18n, BackOffice
|
||||
|
||||
---
|
||||
|
||||
## Базовые URL
|
||||
|
||||
| Бренд | Dev | Production |
|
||||
|--------|----------------------------------|----------------------------------|
|
||||
| Dexar | `https://api.dexarmarket.ru:445` | `https://api.dexarmarket.ru:445` |
|
||||
| Novo | `https://api.novo.market:444` | `https://api.novo.market:444` |
|
||||
|
||||
---
|
||||
|
||||
## Глобальные HTTP-заголовки
|
||||
|
||||
Фронтенд **автоматически добавляет** два кастомных заголовка к **каждому API-запросу** через interceptor. Бэкенд должен читать эти заголовки и использовать для фильтрации/перевода ответов.
|
||||
|
||||
| Заголовок | Пример значения | Описание |
|
||||
|---------------|-----------------|-------------------------------------------------------------------|
|
||||
| `X-Region` | `moscow` | ID региона, выбранного пользователем. **Отсутствует** = все регионы. |
|
||||
| `X-Language` | `ru` | Активный язык интерфейса: `ru`, `en` или `hy`. |
|
||||
|
||||
### Поведение бэкенда
|
||||
|
||||
- **`X-Region`**: Если присутствует — фильтровать товары/категории только по этому региону. Если отсутствует — возвращать всё (глобальный каталог).
|
||||
- **`X-Language`**: Если присутствует — возвращать переведённые `name`, `description` и т.д., если переводы существуют. Если отсутствует или `ru` — возвращать на русском (по умолчанию).
|
||||
|
||||
### Требования CORS для этих заголовков
|
||||
|
||||
```
|
||||
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Проверка состояния
|
||||
|
||||
### `GET /ping`
|
||||
|
||||
Простая проверка работоспособности.
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "pong" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Каталог — Категории
|
||||
|
||||
### `GET /category`
|
||||
|
||||
Возвращает все категории верхнего уровня. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"categoryID": 1,
|
||||
"name": "Электроника",
|
||||
"parentID": 0,
|
||||
"icon": "https://...",
|
||||
"wideBanner": "https://...",
|
||||
"itemCount": 42,
|
||||
"priority": 10,
|
||||
|
||||
"id": "cat_abc123",
|
||||
"visible": true,
|
||||
"img": "https://...",
|
||||
"projectId": "proj_xyz",
|
||||
"subcategories": [
|
||||
{
|
||||
"id": "sub_001",
|
||||
"name": "Смартфоны",
|
||||
"visible": true,
|
||||
"priority": 5,
|
||||
"img": "https://...",
|
||||
"categoryId": "cat_abc123",
|
||||
"parentId": "cat_abc123",
|
||||
"itemCount": 20,
|
||||
"hasItems": true,
|
||||
"subcategories": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Объект Category:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|------------------|---------------|---------|-----------------------------------------------------|
|
||||
| `categoryID` | number | да | Числовой ID (legacy) |
|
||||
| `name` | string | да | Название категории (переведённое если `X-Language`) |
|
||||
| `parentID` | number | да | ID родительской категории (`0` = верхний уровень) |
|
||||
| `icon` | string | нет | URL иконки категории |
|
||||
| `wideBanner` | string | нет | URL широкого баннера |
|
||||
| `itemCount` | number | нет | Количество товаров в категории |
|
||||
| `priority` | number | нет | Приоритет сортировки (больше = выше) |
|
||||
| `id` | string | нет | Строковый ID из BackOffice |
|
||||
| `visible` | boolean | нет | Видима ли категория (по умолч. `true`) |
|
||||
| `img` | string | нет | URL изображения из BackOffice (маппится на `icon`) |
|
||||
| `projectId` | string | нет | Ссылка на проект в BackOffice |
|
||||
| `subcategories` | Subcategory[] | нет | Вложенные подкатегории |
|
||||
|
||||
**Объект Subcategory:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|------------------|---------------|---------|----------------------------------|
|
||||
| `id` | string | да | ID подкатегории |
|
||||
| `name` | string | да | Отображаемое название |
|
||||
| `visible` | boolean | нет | Видима ли |
|
||||
| `priority` | number | нет | Приоритет сортировки |
|
||||
| `img` | string | нет | URL изображения |
|
||||
| `categoryId` | string | да | ID родительской категории |
|
||||
| `parentId` | string | да | ID прямого родителя |
|
||||
| `itemCount` | number | нет | Количество товаров |
|
||||
| `hasItems` | boolean | нет | Есть ли товары |
|
||||
| `subcategories` | Subcategory[] | нет | Вложенные дочерние подкатегории |
|
||||
|
||||
---
|
||||
|
||||
### `GET /category/:categoryID`
|
||||
|
||||
Возвращает товары определённой категории. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Query-параметры:**
|
||||
|
||||
| Параметр | Тип | По умолч. | Описание |
|
||||
|----------|--------|-----------|-----------------------|
|
||||
| `count` | number | `50` | Товаров на страницу |
|
||||
| `skip` | number | `0` | Смещение для пагинации |
|
||||
|
||||
**Ответ `200`:** Массив объектов [Item](#объект-item).
|
||||
|
||||
---
|
||||
|
||||
## 3. Товары
|
||||
|
||||
### `GET /item/:itemID`
|
||||
|
||||
Возвращает один товар. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Ответ `200`:** Один объект [Item](#объект-item).
|
||||
|
||||
---
|
||||
|
||||
### `GET /searchitems`
|
||||
|
||||
Полнотекстовый поиск по товарам. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Query-параметры:**
|
||||
|
||||
| Параметр | Тип | По умолч. | Описание |
|
||||
|----------|--------|-----------|-------------------------------|
|
||||
| `search` | string | — | Поисковый запрос (обязателен) |
|
||||
| `count` | number | `50` | Товаров на страницу |
|
||||
| `skip` | number | `0` | Смещение для пагинации |
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{
|
||||
"items": [ /* объекты Item */ ],
|
||||
"total": 128,
|
||||
"count": 50,
|
||||
"skip": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /randomitems`
|
||||
|
||||
Возвращает случайные товары для карусели/рекомендаций. Учитывает заголовки `X-Region` и `X-Language`.
|
||||
|
||||
**Query-параметры:**
|
||||
|
||||
| Параметр | Тип | По умолч. | Описание |
|
||||
|------------|--------|-----------|--------------------------------------|
|
||||
| `count` | number | `5` | Количество товаров |
|
||||
| `category` | number | — | Ограничить данной категорией (опц.) |
|
||||
|
||||
**Ответ `200`:** Массив объектов [Item](#объект-item).
|
||||
|
||||
---
|
||||
|
||||
### Объект Item
|
||||
|
||||
Бэкенд может возвращать товары в **любом** из двух форматов — legacy или BackOffice. Фронтенд нормализует оба варианта.
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryID": 1,
|
||||
"itemID": 123,
|
||||
"name": "iPhone 15 Pro",
|
||||
"photos": [{ "url": "https://..." }],
|
||||
"description": "Описание товара",
|
||||
"currency": "RUB",
|
||||
"price": 89990,
|
||||
"discount": 10,
|
||||
"remainings": "high",
|
||||
"rating": 4.5,
|
||||
"callbacks": [
|
||||
{
|
||||
"rating": 5,
|
||||
"content": "Отличный товар!",
|
||||
"userID": "user_123",
|
||||
"timestamp": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"questions": [
|
||||
{
|
||||
"question": "Есть ли гарантия?",
|
||||
"answer": "Да, 12 месяцев",
|
||||
"upvotes": 5,
|
||||
"downvotes": 0
|
||||
}
|
||||
],
|
||||
|
||||
"id": "item_abc123",
|
||||
"visible": true,
|
||||
"priority": 10,
|
||||
"imgs": ["https://img1.jpg", "https://img2.jpg"],
|
||||
"tags": ["new", "popular"],
|
||||
"badges": ["bestseller", "sale"],
|
||||
"simpleDescription": "Краткое описание",
|
||||
"descriptionFields": [
|
||||
{ "key": "Процессор", "value": "A17 Pro" },
|
||||
{ "key": "Память", "value": "256 GB" }
|
||||
],
|
||||
"subcategoryId": "sub_001",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Short description",
|
||||
"description": [
|
||||
{ "key": "Processor", "value": "A17 Pro" }
|
||||
]
|
||||
},
|
||||
"hy": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Կարcheck check"
|
||||
}
|
||||
},
|
||||
"comments": [
|
||||
{
|
||||
"id": "cmt_001",
|
||||
"text": "Отличный товар!",
|
||||
"author": "user_123",
|
||||
"stars": 5,
|
||||
"createdAt": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"quantity": 50
|
||||
}
|
||||
```
|
||||
|
||||
**Все поля Item:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|---------------------|-------------------|---------|-----------------------------------------------------------|
|
||||
| `categoryID` | number | да | Категория, к которой принадлежит товар |
|
||||
| `itemID` | number | да | Числовой ID товара (legacy) |
|
||||
| `name` | string | да | Название товара |
|
||||
| `photos` | Photo[] | нет | Массив фотографий `[{ url }]` (legacy) |
|
||||
| `description` | string | да | Текстовое описание |
|
||||
| `currency` | string | да | Код валюты (по умолч. `RUB`) |
|
||||
| `price` | number | да | Цена |
|
||||
| `discount` | number | да | Процент скидки (`0`–`100`) |
|
||||
| `remainings` | string | нет | Уровень остатка: `high`, `medium`, `low`, `out` |
|
||||
| `rating` | number | да | Средний рейтинг (`0`–`5`) |
|
||||
| `callbacks` | Review[] | нет | Отзывы (legacy формат) |
|
||||
| `questions` | Question[] | нет | Вопросы и ответы |
|
||||
| `id` | string | нет | Строковый ID из BackOffice |
|
||||
| `visible` | boolean | нет | Виден ли товар (по умолч. `true`) |
|
||||
| `priority` | number | нет | Приоритет сортировки (больше = выше) |
|
||||
| `imgs` | string[] | нет | URL картинок из BackOffice (маппится на `photos`) |
|
||||
| `tags` | string[] | нет | Теги для фильтрации |
|
||||
| `badges` | string[] | нет | Бейджи (`bestseller`, `sale` и т.д.) |
|
||||
| `simpleDescription` | string | нет | Краткое текстовое описание |
|
||||
| `descriptionFields` | DescriptionField[]| нет | Структурированное описание `[{ key, value }]` |
|
||||
| `subcategoryId` | string | нет | Ссылка на подкатегорию из BackOffice |
|
||||
| `translations` | Record | нет | Переводы по ключу языка (см. ниже) |
|
||||
| `comments` | Comment[] | нет | Комментарии в формате BackOffice |
|
||||
| `quantity` | number | нет | Числовое кол-во на складе (маппится на `remainings`) |
|
||||
|
||||
**Вложенные типы:**
|
||||
|
||||
| Тип | Поля |
|
||||
|--------------------|-----------------------------------------------------------------|
|
||||
| `Photo` | `url: string`, `photo?: string`, `video?: string`, `type?: string` |
|
||||
| `DescriptionField` | `key: string`, `value: string` |
|
||||
| `Comment` | `id?: string`, `text: string`, `author?: string`, `stars?: number`, `createdAt?: string` |
|
||||
| `Review` | `rating?: number`, `content?: string`, `userID?: string`, `answer?: string`, `timestamp?: string` |
|
||||
| `Question` | `question: string`, `answer: string`, `upvotes: number`, `downvotes: number` |
|
||||
| `ItemTranslation` | `name?: string`, `simpleDescription?: string`, `description?: DescriptionField[]` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Корзина
|
||||
|
||||
### `POST /cart` — Добавить товар в корзину
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{ "itemID": 123, "quantity": 1 }
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Added to cart" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `PATCH /cart` — Обновить количество товара
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{ "itemID": 123, "quantity": 3 }
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Updated" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `DELETE /cart` — Удалить товары из корзины
|
||||
|
||||
**Тело запроса:** Массив ID товаров
|
||||
```json
|
||||
[123, 456]
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Removed" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /cart` — Получить содержимое корзины
|
||||
|
||||
**Ответ `200`:** Массив объектов [Item](#объект-item) (каждый с полем `quantity`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Оплата (СБП / QR)
|
||||
|
||||
### `POST /cart` — Создать платёж (СБП QR)
|
||||
|
||||
> Примечание: Тот же эндпоинт что и добавление в корзину, но с другой схемой тела запроса.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"amount": 89990,
|
||||
"currency": "RUB",
|
||||
"siteuserID": "tg_123456789",
|
||||
"siteorderID": "order_abc123",
|
||||
"redirectUrl": "",
|
||||
"telegramUsername": "john_doe",
|
||||
"items": [
|
||||
{ "itemID": 123, "price": 89990, "name": "iPhone 15 Pro" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{
|
||||
"qrId": "qr_abc123",
|
||||
"qrStatus": "CREATED",
|
||||
"qrExpirationDate": "2026-02-28T13:00:00Z",
|
||||
"payload": "https://qr.nspk.ru/...",
|
||||
"qrUrl": "https://qr.nspk.ru/..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /qr/payment/:qrId` — Проверить статус оплаты
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{
|
||||
"additionalInfo": "",
|
||||
"paymentPurpose": "Order #order_abc123",
|
||||
"amount": 89990,
|
||||
"code": "SUCCESS",
|
||||
"createDate": "2026-02-28T12:00:00Z",
|
||||
"currency": "RUB",
|
||||
"order": "order_abc123",
|
||||
"paymentStatus": "COMPLETED",
|
||||
"qrId": "qr_abc123",
|
||||
"transactionDate": "2026-02-28T12:01:00Z",
|
||||
"transactionId": 999,
|
||||
"qrExpirationDate": "2026-02-28T13:00:00Z",
|
||||
"phoneNumber": "+7XXXXXXXXXX"
|
||||
}
|
||||
```
|
||||
|
||||
| Значение `paymentStatus` | Значение |
|
||||
|--------------------------|------------------------------|
|
||||
| `CREATED` | QR создан, не оплачен |
|
||||
| `WAITING` | Оплата в процессе |
|
||||
| `COMPLETED` | Оплата успешна |
|
||||
| `EXPIRED` | QR-код истёк |
|
||||
| `CANCELLED` | Оплата отменена |
|
||||
|
||||
---
|
||||
|
||||
### `POST /purchase-email` — Отправить email после оплаты
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"telegramUserId": "123456789",
|
||||
"items": [
|
||||
{ "itemID": 123, "name": "iPhone 15 Pro", "price": 89990, "currency": "RUB" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Email sent" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Отзывы / Комментарии
|
||||
|
||||
### `POST /comment` — Оставить отзыв
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"itemID": 123,
|
||||
"rating": 5,
|
||||
"comment": "Отличный товар!",
|
||||
"username": "john_doe",
|
||||
"userId": 123456789,
|
||||
"timestamp": "2026-02-28T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Review submitted" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Регионы
|
||||
|
||||
### `GET /regions` — Список доступных регионов
|
||||
|
||||
Возвращает регионы, в которых работает маркетплейс.
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "moscow",
|
||||
"city": "Москва",
|
||||
"country": "Россия",
|
||||
"countryCode": "RU",
|
||||
"timezone": "Europe/Moscow"
|
||||
},
|
||||
{
|
||||
"id": "spb",
|
||||
"city": "Санкт-Петербург",
|
||||
"country": "Россия",
|
||||
"countryCode": "RU",
|
||||
"timezone": "Europe/Moscow"
|
||||
},
|
||||
{
|
||||
"id": "yerevan",
|
||||
"city": "Ереван",
|
||||
"country": "Армения",
|
||||
"countryCode": "AM",
|
||||
"timezone": "Asia/Yerevan"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Объект Region:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|---------------|--------|---------|----------------------------------|
|
||||
| `id` | string | да | Уникальный идентификатор региона |
|
||||
| `city` | string | да | Название города |
|
||||
| `country` | string | да | Название страны |
|
||||
| `countryCode` | string | да | Код страны ISO 3166-1 alpha-2 |
|
||||
| `timezone` | string | нет | Часовой пояс IANA |
|
||||
|
||||
> **Фоллбэк:** Если эндпоинт недоступен, фронтенд использует 6 захардкоженных значений: Москва, СПб, Ереван, Минск, Алматы, Тбилиси.
|
||||
|
||||
---
|
||||
|
||||
## 8. Авторизация (вход через Telegram)
|
||||
|
||||
Авторизация **через Telegram** с **cookie-сессиями** (HttpOnly, Secure, SameSite=None).
|
||||
|
||||
Все auth-эндпоинты должны поддерживать CORS с `credentials: true`.
|
||||
|
||||
### Процесс авторизации
|
||||
|
||||
```
|
||||
1. Пользователь нажимает «Оформить заказ» → не авторизован → показывается диалог входа
|
||||
2. Нажимает «Войти через Telegram» → открывается https://t.me/{bot}?start=auth_{callback}
|
||||
3. Пользователь запускает бота в Telegram
|
||||
4. Бот отправляет данные пользователя → бэкенд /auth/telegram/callback
|
||||
5. Бэкенд создаёт сессию → устанавливает Set-Cookie
|
||||
6. Фронтенд опрашивает GET /auth/session каждые 3 секунды
|
||||
7. Сессия обнаружена → диалог закрывается → оформление заказа продолжается
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /auth/session` — Проверить текущую сессию
|
||||
|
||||
**Запрос:** Только cookie (сессионная cookie, установленная бэкендом).
|
||||
|
||||
**Ответ `200`** (авторизован):
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "john_doe",
|
||||
"displayName": "John Doe",
|
||||
"active": true,
|
||||
"expiresAt": "2026-03-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `200`** (сессия истекла):
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"telegramUserId": 123456789,
|
||||
"username": "john_doe",
|
||||
"displayName": "John Doe",
|
||||
"active": false,
|
||||
"expiresAt": "2026-02-27T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ `401`** (нет сессии):
|
||||
```json
|
||||
{ "error": "No active session" }
|
||||
```
|
||||
|
||||
**Объект AuthSession:**
|
||||
|
||||
| Поле | Тип | Обязат. | Описание |
|
||||
|------------------|---------|---------|-------------------------------------------|
|
||||
| `sessionId` | string | да | Уникальный ID сессии |
|
||||
| `telegramUserId` | number | да | ID пользователя в Telegram |
|
||||
| `username` | string? | нет | @username в Telegram (может быть null) |
|
||||
| `displayName` | string | да | Отображаемое имя (имя + фамилия) |
|
||||
| `active` | boolean | да | Действительна ли сессия |
|
||||
| `expiresAt` | string | да | Дата истечения в формате ISO 8601 |
|
||||
|
||||
---
|
||||
|
||||
### `GET /auth/telegram/callback` — Callback авторизации Telegram-бота
|
||||
|
||||
Вызывается Telegram-ботом после авторизации пользователя.
|
||||
|
||||
**Тело запроса (от бота):**
|
||||
```json
|
||||
{
|
||||
"id": 123456789,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"username": "john_doe",
|
||||
"photo_url": "https://t.me/i/userpic/...",
|
||||
"auth_date": 1709100000,
|
||||
"hash": "abc123def456..."
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ:** Должен установить cookie сессии и вернуть:
|
||||
```json
|
||||
{
|
||||
"sessionId": "sess_abc123",
|
||||
"message": "Authenticated successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Требования к cookie:**
|
||||
|
||||
| Атрибут | Значение | Примечание |
|
||||
|------------|----------------|-----------------------------------------------------|
|
||||
| `HttpOnly` | `true` | Недоступна из JavaScript |
|
||||
| `Secure` | `true` | Только HTTPS |
|
||||
| `SameSite` | `None` | Обязательно для cross-origin (API ≠ фронтенд) |
|
||||
| `Path` | `/` | |
|
||||
| `Max-Age` | `86400` (24ч) | Или по необходимости |
|
||||
|
||||
---
|
||||
|
||||
### `POST /auth/logout` — Завершить сессию
|
||||
|
||||
**Запрос:** Только cookie, пустое тело `{}`
|
||||
|
||||
**Ответ `200`:**
|
||||
```json
|
||||
{ "message": "Logged out" }
|
||||
```
|
||||
|
||||
Должен очистить/инвалидировать cookie сессии.
|
||||
|
||||
---
|
||||
|
||||
### Обновление сессии
|
||||
|
||||
Фронтенд повторно проверяет сессию за **60 секунд до `expiresAt`**. Если бэкенд поддерживает скользящий срок действия (sliding expiration), можно обновлять `Max-Age` cookie при каждом вызове `GET /auth/session`.
|
||||
|
||||
---
|
||||
|
||||
## 9. i18n / Переводы
|
||||
|
||||
Фронтенд поддерживает 3 языка: **Русский (ru)**, **Английский (en)**, **Армянский (hy)**.
|
||||
|
||||
Активный язык отправляется через HTTP-заголовок `X-Language` с каждым запросом.
|
||||
|
||||
### Что бэкенд должен делать с `X-Language`
|
||||
|
||||
1. **Категории и товары**: Если для запрошенного языка есть поле `translations`, вернуть переведённые `name`, `description` и т.д. ИЛИ бэкенд может применять переводы на стороне сервера и возвращать уже переведённые поля.
|
||||
|
||||
2. **Поле `translations`** на товарах (опциональный подход):
|
||||
```json
|
||||
{
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Short desc in English",
|
||||
"description": [{ "key": "Processor", "value": "A17 Pro" }]
|
||||
},
|
||||
"hy": {
|
||||
"name": "iPhone 15 Pro",
|
||||
"simpleDescription": "Կarcheck check"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Рекомендуемый подход**: Читать заголовок `X-Language` и возвращать `name`/`description` на этом языке напрямую. Если перевода нет — возвращать русский вариант по умолчанию.
|
||||
|
||||
---
|
||||
|
||||
## 10. Настройка CORS
|
||||
|
||||
Для работы auth-cookie и кастомных заголовков конфигурация CORS бэкенда должна включать:
|
||||
|
||||
```
|
||||
Access-Control-Allow-Origin: https://dexarmarket.ru (НЕ wildcard *)
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Headers: Content-Type, X-Region, X-Language
|
||||
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
|
||||
```
|
||||
|
||||
> **Важно:** `Access-Control-Allow-Origin` не может быть `*` при `Allow-Credentials: true`. Должен быть точный origin фронтенда.
|
||||
|
||||
**Разрешённые origins:**
|
||||
- `https://dexarmarket.ru`
|
||||
- `https://novo.market`
|
||||
- `http://localhost:4200` (dev)
|
||||
- `http://localhost:4201` (dev, Novo)
|
||||
|
||||
---
|
||||
|
||||
## 11. Настройка Telegram-бота
|
||||
|
||||
Каждому бренду нужен свой бот:
|
||||
- **Dexar:** `@dexarmarket_bot`
|
||||
- **Novo:** `@novomarket_bot`
|
||||
|
||||
Бот должен:
|
||||
1. Слушать команду `/start auth_{callbackUrl}`
|
||||
2. Извлечь callback URL
|
||||
3. Отправить данные пользователя (`id`, `first_name`, `username` и т.д.) на этот callback URL
|
||||
4. Callback URL: `{apiUrl}/auth/telegram/callback`
|
||||
|
||||
---
|
||||
|
||||
## Полный справочник эндпоинтов
|
||||
|
||||
### Новые эндпоинты
|
||||
|
||||
| Метод | Путь | Описание | Авторизация |
|
||||
|--------|---------------------------|---------------------------------|-------------|
|
||||
| `GET` | `/regions` | Список доступных регионов | Нет |
|
||||
| `GET` | `/auth/session` | Проверка текущей сессии | Cookie |
|
||||
| `GET` | `/auth/telegram/callback` | Callback авторизации через бота | Нет (бот) |
|
||||
| `POST` | `/auth/logout` | Завершение сессии | Cookie |
|
||||
|
||||
### Существующие эндпоинты
|
||||
|
||||
| Метод | Путь | Описание | Авт. | Заголовки |
|
||||
|----------|-----------------------|---------------------------|------|--------------------|
|
||||
| `GET` | `/ping` | Проверка состояния | Нет | — |
|
||||
| `GET` | `/category` | Список категорий | Нет | X-Region, X-Language |
|
||||
| `GET` | `/category/:id` | Товары категории | Нет | X-Region, X-Language |
|
||||
| `GET` | `/item/:id` | Один товар | Нет | X-Region, X-Language |
|
||||
| `GET` | `/searchitems` | Поиск товаров | Нет | X-Region, X-Language |
|
||||
| `GET` | `/randomitems` | Случайные товары | Нет | X-Region, X-Language |
|
||||
| `POST` | `/cart` | Добавить в корзину / Оплата | Нет* | — |
|
||||
| `PATCH` | `/cart` | Обновить кол-во | Нет* | — |
|
||||
| `DELETE` | `/cart` | Удалить из корзины | Нет* | — |
|
||||
| `GET` | `/cart` | Содержимое корзины | Нет* | — |
|
||||
| `POST` | `/comment` | Оставить отзыв | Нет | — |
|
||||
| `GET` | `/qr/payment/:qrId` | Статус оплаты | Нет | — |
|
||||
| `POST` | `/purchase-email` | Отправить email после оплаты | Нет | — |
|
||||
|
||||
> \* Эндпоинты корзины/оплаты могут использовать cookie сессии (если есть) для привязки к заказу, но не требуют авторизации строго. Фронтенд проверяет авторизацию перед оформлением заказа.
|
||||
Reference in New Issue
Block a user