Обзор

REST JSON API для мобильного приложения IPR SMART. Совместим по маршрутам и формату ответов с legacy old-mobible-api, работает с базой данных ipr_smart.

ПараметрЗначение
Базовый URL (dev)http://localhost:8000
Базовый URL (prod)https://mobile2.iprbookshop.ru
Префикс APIНет — маршруты в корне (/users/auth, не /api/...)
Канонические пути v3С префиксом /v3/
ДокументацияGET /docs
Health checkGET /up
При VERSION-API: 3 запросы к legacy-путям (например /books/catalog) автоматически перенаправляются (302) на /v3/books/catalog.

Установка и запуск

composer install
cp .env.example .env        # Windows: copy .env.example .env
php artisan key:generate

Настройте .env (см. раздел ниже), затем:

php artisan serve
# → http://localhost:8000

Для production: document root указывает на каталог public/. CI деплоит на mobile2.iprbookshop.ru (master) и mobile.dev.iprbookshop.ru (develop).

Переменные окружения

Для MariaDB/MySQL 5.7 в OSPanel укажите DB_COLLATION=utf8mb4_unicode_ci (MySQL 8 collation utf8mb4_0900_ai_ci не поддерживается).

ПеременнаяОбязательнаОписание
DB_*ДаПодключение к MySQL/MariaDB, база ipr_smart
DB_COLLATIONРекомендуетсяutf8mb4_unicode_ci для MariaDB 10.x
SEARCH_API_URLДаURL внешнего поискового сервиса (каталоги книг/журналов)
MOBILE_AUTH_SALTДаСоль для токенов и legacy-паролей
PAGE_SIZEНетРазмер страницы пагинации (по умолчанию 50)
CONTENT_PATHДа*Путь к файлам публикаций (PDF/EPUB)
OAUTH_TOKENДля OAuthСекрет для GET /oauth
APP_SOURCE_URLНетБазовый URL ссылок на публикации
APP_DEBUGНетПри true отсутствие VERSION-API трактуется как версия 3

Заголовки и версии API

Почти все запросы обязаны содержать заголовок:

VERSION-API: 3

Исключение: GET /oauth — заголовок не требуется (как в old-mobible-api).

ВерсияФормат ответаАвторизация токеном
1Legacy (success: "true")Только uid
2Legacyuid + deviceId + accessToken + date
3v3 (meta + data)Как версия 2 + редирект на /v3/
Неверный или отсутствующий VERSION-API (при APP_DEBUG=false) возвращает HTTP 404.

Авторизация

Публичные маршруты (с VERSION-API)

Параметры uid / deviceId / accessToken не нужны:

Без VERSION-API

Защищённые маршруты

Middleware ApiAccess проверяет следующие параметры (query string или body):

ПараметрОбязателенОписание
uidДаЧисловой ID пользователя
deviceIdДа (v2+)Идентификатор устройства (тот же, что при авторизации)
accessTokenДа (v2+)Ежедневный токен (см. формулу ниже)
dateНетДата в формате Ymd (по умолчанию — сегодня)

Формула токена доступа

accessToken = md5(MOBILE_AUTH_SALT + deviceToken + deviceId + date) где: deviceToken — значение accessToken из ответа POST /users/auth deviceId — идентификатор устройства date — Ymd, например 20250714

Поток авторизации

1. POST /users/auth → получить uid, accessToken (deviceToken) 2. Вычислить дневной токен по формуле выше 3. Передавать uid, deviceId, accessToken, date на все защищённые запросы

Пароли при входе

Формат ответов

v3 (VERSION-API: 3)

{
  "meta": { "success": true, "message": "" },
  "data": { ... }
}

Ошибка:

{
  "meta": { "success": false, "message": "Текст ошибки" },
  "data": null
}

Legacy (VERSION-API: 1–2)

{ "success": "true", "message": "", ... }
{ "success": "false", "message": "Текст ошибки" }

Авторизация (POST /users/auth) при legacy оборачивает данные в ключ user:

{
  "success": "true",
  "message": "",
  "user": {
    "username": "...", "uid": 123, "accessToken": "...",
    "cid": 45, "fullname": "...", "org_name": "..."
  }
}

Исключения из общих правил

ЭндпоинтФормат
GET /users/statusВсегда legacy, даже при VERSION-API: 3
GET /oauth{ success: bool, message, data? } — boolean, не строка
POST /favorite/uploads с listIdВсегда legacy { success: "true", message }
Цитаты, закладки, рейтинг, контентСобственный { meta, data } без повторной обёртки ApiController
GET /v3/autocompleteВсегда v3-обёртка

Пагинация

{
  "meta": { "success": true, "message": "" },
  "data": {
    "total": 100,
    "per_page": 50,
    "current_page": 1,
    "last_page": 2,
    "next_page_url": "...",
    "prev_page_url": null,
    "from": 1,
    "to": 50,
    "data": [ ... ]
  }
}

Исключение: GET /users/status всегда возвращает legacy-формат независимо от версии API (см. таблицу выше).

Совместимость с old-mobible-api

API реализован как замена legacy old-mobible-api с сохранением маршрутов, имён полей (pagetitle, tv_*) и форматов ответов.

v3 (основной сценарий мобильного приложения) — покрытие ~95%: пользователи, каталоги, поиск, избранное, цитаты, закладки, рейтинг.

Сводка по группам маршрутов

ГруппаСтатусПримечание
Users + OAuthСовпадаетOAuth без VERSION-API; legacy auth → ключ user
v3 Books / Journals / NumbersСовпадаетLegacy-пути редиректятся на /v3/
v3 SearchЧастичноТот же маршрут и параметры; backend — Search API вместо Sphinx
FavoritesСовпадаетlistId + uploads[]; type 1/2 для номеров
Quotes / Bookmarks / RatingСовпадаетЦитаты отдают ext_id; закладки без хранения text
GET /downloadСовпадаетXOR-шифрованный PDF, ключ — первые 16 символов device token
GET /search/prepareНе реализованКлиент не вызывает
GET /ugnp/sНе реализованКлиент читает локальный ugnp.json
POST /send/requestСовпадаетОбращение в поддержку
Legacy GET /search (v1–2)ЧастичноСтарый формат { total, search[] } с фильтрами не воспроизведён

Значения selection_options (каталог книг)

ЗначениеФильтр
1Приоритетные (sort_priority = 1)
2Рекомендованные
3Новинки (за 30 дней)
4Популярные (read_count > 200)
5Часто добавляемые в избранное

Пользователи

POST /users/auth Публичный

Авторизация пользователя и регистрация устройства.

ПараметрТипОбязателенОписание
usernamestringДаЛогин или email
passwdstringДаПароль (см. раздел авторизации)
device_idstringДаУникальный ID устройства

Ответ v3 (data):

{
  "meta": { "success": true, "message": "" },
  "data": {
    "username": "user@example.com",
    "uid": 123,
    "cid": 45,
    "fullname": "Иванов И.И.",
    "org_name": "Университет",
    "email": "user@example.com",
    "blockedAfter": 1735689600,
    "blockedHash": "abc...",
    "accessToken": "device_token_from_db",
    "clientLogotype": "https://...",
    "clientColor": "ff9900"
  }
}

При VERSION-API: 1–2 те же поля находятся внутри ключа user (см. раздел «Формат ответов»).

GET /users/status Публичный

Проверка статуса подписки и версии приложения.

ПараметрТипОбязателенОписание
uidintДаID пользователя
{
  "success": "true",
  "message": "",
  "version": { "version": 13, "strong": true },
  "username": "Иванов И.И.",
  "org_name": "Университет",
  "clientLogotype": "https://...",
  "clientColor": "ff9900"
}
GET /users/check Публичный

Проверка учётных данных приглашённого пользователя (до регистрации). Для уже зарегистрированных учёток используйте POST /users/auth.

ПараметрТипОбязателенОписание
usernamestringДаЛогин/email приглашения
password / passwdstringДаПароль приглашения
GET /users/restore Публичный

Восстановление пароля — генерирует новый и отправляет на email.

ПараметрТипОбязателенОписание
emailstringДаEmail зарегистрированного пользователя
POST / GET /users/create Публичный

Регистрация нового пользователя по приглашению. Поддерживаются POST и GET (query-параметры, как в legacy-клиентах).

ПараметрТипОбязателенОписание
usernamestringДаЛогин приглашения
password / passwdstringДаПароль приглашения
emailstringДаEmail нового пользователя
fullnamestringДаФИО
usertypeintДаТип пользователя: 1 — студент, 2 — аспирант, 3 — преподаватель
user_passwordstringДаНовый пароль (мин. 6 символов)
GET /oauth Публичный

OAuth-авторизация для внешних систем. Заголовок VERSION-API не требуется.

ПараметрТипОбязателенОписание
usernamestringДаЛогин/email
passwordstringДаПароль (сравнивается как md5(password) с БД)
timestringДаВременная метка
hashstringДаmd5(OAUTH_TOKEN + time)

Книги

Все маршруты защищённые. Канонический путь: /v3/books/...

GET /v3/books/catalog Защищённый

Каталог книг организации (через Search API).

ПараметрТипОписание
pageintНомер страницы (по умолчанию 1)
ugnpstringКод УГНП (фильтр, 00 = все)
titlestringНазвание
authorsstringАвторы
pubhousestringИздательство
year_leftintГод от
year_rightintГод до
pub_typesstringТипы изданий через ||, напр. учебник||монография
selection_optionsintПодборка: 1–5 (см. раздел «Совместимость»). Алиас: selected_options

Элемент data.data[]: id, pagetitle, tv_year, tv_desc, tv_liability, tv_author

GET /v3/books/{id} Защищённый

Карточка книги с оглавлением, рейтингом и статусом избранного.

ПараметрТипОписание
idint (path)ID книги

Дополнительные поля: isFavorite ("0"/"1"), size, content (оглавление), rating, user_rating

GET /v3/books/favorite Защищённый

Избранные книги пользователя.

ПараметрТипОписание
pageintСтраница
last_idintID последней записи (для инкрементальной подгрузки)
GET /v3/autocomplete Защищённый

Автодополнение для фильтров каталога.

ПараметрТипОписание
typeint13 — книги, 28 — журналы
fieldstringpagetitle, tv_author, tv_pubhouse
querystringСтрока поиска

Журналы

GET /v3/journals/catalog Защищённый
ПараметрТипОписание
pageintСтраница
titlestringНазвание
pubhousestringИздательство
ugnpstringУГНП (по умолчанию 00)

Элемент data.data[]: id, pagetitle, tv_year, tv_desc, tv_pubhouse, pubhouse, image

GET /v3/journals/{id} Защищённый

Карточка журнала со списком доступных годов.

Поля: id, pagetitle, tv_desc, tv_pubhouse, pubhouse, image, years[] (id, pagetitle)

yearId = periodical_id × 10000 + year. Пример: журнал 100, год 2024 → 100012024
GET /v3/journals/favorite Защищённый

Избранные номера журналов пользователя (как в старом API). Параметры: page, last_id

Номера журналов

GET /v3/years/{yearId} Защищённый

Список номеров журнала за год.

Ответ: id, pagetitle, pagetitle_journal, numbers[]

GET /v3/numbers/{id} Защищённый

Карточка номера журнала (аналогично книге: isFavorite, content, rating).

GET /v3/search Защищённый

Полнотекстовый поиск через Search API.

ПараметрТипОбязателенОписание
sstringДаПоисковый запрос
typeintНет*1 — книги, 2 — номера журналов. Обязателен, если не передан id
pageintНетСтраница результатов каталога (по умолчанию 1)
idintНетID издания: искать слово в оглавлении/страницах этой публикации

Избранное

Тип публикации: type=1 — книга, type=2 — журнал или номер. Если type не указан, тип определяется по ID.

PUT / GET /favorite/{id} Защищённый

Добавить в избранное.

ПараметрТипОписание
idint (path)ID публикации
typeint1 (книга) или 2 (журнал/номер). Если не передан — определяется по ID
DELETE /favorite/{id} Защищённый

Удалить из избранного. Параметр type — как выше.

Если издания не было в избранном: meta.message = «Издание не находилсь в избранном».

POST /favorite/uploads Защищённый

Пакетная синхронизация избранного. Поддерживаются два формата тела запроса.

Legacy (old-mobible-api): параметр listId — массив или одно значение ID книг. Ответ всегда legacy:

listId[]=123&listId[]=456&uid=123
→ { "success": "true", "message": "Издания успешно добавлены!" }

Расширенный формат:

{
  "uploads": [
    { "id": 123, "type": 1, "action": "add" },
    { "id": 456, "type": 2, "action": "delete" }
  ]
}

type: 1 — книга, 2 — номер журнала. action: add или delete.

Цитаты, закладки, контент, рейтинг

Маршруты на уровне корня (не внутри /books/). {id} — ID публикации (книги).

GET /v3/{id}/quotes/get Защищённый

Список цитат. Элемент: ext_id, page, text, create_date

GET /v3/{id}/quotes/add Защищённый
ПараметрОписание
page_idНомер страницы
textТекст цитаты
GET /v3/{id}/quotes/delete Защищённый

Параметр: quote_id

GET /v3/quotes/{id}/update Защищённый

Обновить цитату. Параметр: text

GET /v3/{id}/bookmarks/get Защищённый

Список закладок (формат аналогичен цитатам).

GET /v3/{id}/bookmarks/add Защищённый

Добавить закладку.

ПараметрОписание
page_idНомер страницы (обязателен)
textКомментарий (в legacy API; в новой схеме не сохраняется)
GET /v3/{id}/bookmarks/delete Защищённый

Параметр: bookmark_id

GET /v3/{id}/content/download Защищённый

Оглавление публикации. Ответ: [{ "page": 1, "text": "Глава 1" }, ...]

GET /v3/{id}/content/search Защищённый

Поиск слова в оглавлении/тексте страниц издания.

ПараметрОписание
sИскомое слово или фраза
pageОпционально: искать только на этой странице

Ответ: [{ "page": 15, "text": "..." }, ...]

GET /v3/{id}/rating/update Защищённый

Установить оценку. Параметр: value (1–5).

{
  "meta": { "success": true, "message": "" },
  "data": { "value": 4.5, "user_value": 5 }
}

Примеры запросов

1. Авторизация

passwd — не открытый пароль, а md5(md5(plain) + MOBILE_AUTH_SALT):

curl -X POST "http://localhost:8000/users/auth" \
  -H "VERSION-API: 3" \
  -d "username=user@example.com" \
  -d "passwd=COMPUTED_ANDROID_HASH" \
  -d "device_id=my-device-001"

2. Вычисление токена (PHP)

$salt = 'Ga90IfDBzo2Mqcva';       // MOBILE_AUTH_SALT
$deviceToken = '...';              // accessToken из ответа auth
$deviceId = 'my-device-001';
$date = date('Ymd');

$token = md5($salt . $deviceToken . $deviceId . $date);

3. Каталог книг

curl "http://localhost:8000/v3/books/catalog?page=1&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN&date=20250714" \
  -H "VERSION-API: 3"

4. Карточка книги

curl "http://localhost:8000/v3/books/42?uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN" \
  -H "VERSION-API: 3"

5. Поиск

curl "http://localhost:8000/v3/search?s=математика&type=1&page=1&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN" \
  -H "VERSION-API: 3"

6. Добавить в избранное

curl -X PUT "http://localhost:8000/favorite/42?type=1&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN" \
  -H "VERSION-API: 3"

7. Синхронизация избранного (legacy listId)

curl -X POST "http://localhost:8000/favorite/uploads" \
  -H "VERSION-API: 3" \
  -d "listId[]=42&listId[]=43&uid=123&deviceId=my-device-001&accessToken=COMPUTED_TOKEN"

8. OAuth (без VERSION-API)

curl "http://localhost:8000/oauth?username=user&password=secret&time=1234567890&hash=COMPUTED_HASH"

Типичные ошибки

СитуацияОтвет
Нет заголовка VERSION-API (кроме /oauth)HTTP 404
Неверный uidНет данных о пользователе
Неверный токенОшибка авторизации
Неверный пароль при authуказан неверный пароль
Пустой поисковый запросПо данному запросу не найдено изданий.
Search API недоступенОшибка поискового сервиса: ...
Несовместимая collation MySQLУстановите DB_COLLATION=utf8mb4_unicode_ci в .env

IPR SMART Mobile API · Laravel 11.56.1 · Совместимость с old-mobible-api