feat(api): search with Meilisearch (M2)
ci / api / api (push) Successful in 27s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m1s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 34s
ci / ts / ts (packages + web) (push) Successful in 1m1s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 25s

- artists, albums and tracks indexes are rebuilt from PostgreSQL next
  to the live ones and swapped in, so search keeps working; a rebuild
  runs on start, after every scan and when a source is removed
- transliteration fields (Кино ↔ kino) and a retry in the other
  keyboard layout (ghbdtn → привет) reported as corrected_query
- /search/suggest (3 artists, 3 albums, 5 tracks), /search by type
  with cursor pages, /admin/search/reindex; Meilisearch is part of
  /readyz
- found ids are hydrated from the catalog, so a lagging index never
  returns deleted entries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 06:51:50 +03:00
1 parent c583991d14
commit c1160cccd3
16 files changed
+1590 -124

No files matched your search

+101
View File
@@ -14,6 +14,7 @@ tags:
- name: admin
- name: library
- name: catalog
- name: search
security:
- bearerAuth: []
paths:
@@ -665,6 +666,72 @@ paths:
$ref: "#/components/schemas/Track"
default:
$ref: "#/components/responses/Problem"
/api/v1/search/suggest:
get:
operationId: searchSuggest
tags: [search]
summary: Выпадающий список поиска — до 3 исполнителей, 3 альбомов, 5 треков
description: |
Если по запросу ничего не найдено, сервер повторяет его в другой раскладке клавиатуры
(«ghbdtn» → «привет») и возвращает исправленный запрос в `corrected_query`.
parameters:
- name: q
in: query
required: true
schema:
type: string
maxLength: 200
responses:
"200":
description: Подсказки
content:
application/json:
schema:
$ref: "#/components/schemas/SearchResults"
default:
$ref: "#/components/responses/Problem"
/api/v1/search:
get:
operationId: search
tags: [search]
summary: Страница поиска
description: |
`type=all` — по несколько результатов каждого вида без пагинации;
конкретный тип — страницы с `next_cursor`.
parameters:
- name: q
in: query
required: true
schema:
type: string
maxLength: 200
- name: type
in: query
schema:
type: string
enum: [all, artists, albums, tracks]
default: all
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Cursor"
responses:
"200":
description: Результаты
content:
application/json:
schema:
$ref: "#/components/schemas/SearchResults"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/search/reindex:
post:
operationId: reindexSearch
tags: [search]
summary: Пересобрать поисковые индексы (admin)
responses:
"202":
description: Переиндексация запущена
default:
$ref: "#/components/responses/Problem"
components:
parameters:
SourceId:
@@ -1461,6 +1528,40 @@ components:
type: array
items:
$ref: "#/components/schemas/Track"
SearchResults:
type: object
required: [artists, albums, tracks, totals, corrected_query, next_cursor]
properties:
artists:
type: array
items:
$ref: "#/components/schemas/ArtistRef"
albums:
type: array
items:
$ref: "#/components/schemas/AlbumSummary"
tracks:
type: array
items:
$ref: "#/components/schemas/Track"
totals:
type: object
required: [artists, albums, tracks]
description: Оценка общего числа совпадений
properties:
artists:
type: integer
albums:
type: integer
tracks:
type: integer
corrected_query:
type: string
nullable: true
description: Запрос в другой раскладке, если по исходному ничего не нашлось
next_cursor:
type: string
nullable: true
Session:
type: object
required: [id, device_name, client_type, created_at, last_used_at, current]