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
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:
1 parent
c583991d14
commit
c1160cccd3
16 files changed
+1590
-124
No files matched your search
@@ -189,3 +189,56 @@ func (st *Store) Artist(ctx context.Context, id uuid.UUID) (ArtistRow, error) {
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// AlbumsByIDs — альбомы в порядке ids (недостающие пропускаются).
|
||||
func (st *Store) AlbumsByIDs(ctx context.Context, ids []uuid.UUID) ([]AlbumRow, error) {
|
||||
rows, err := st.pool.Query(ctx, albumSelect+" WHERE al.id = ANY($1)", ids)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("library: albums: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
byID := map[uuid.UUID]AlbumRow{}
|
||||
for rows.Next() {
|
||||
a, err := scanAlbum(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byID[a.ID] = a
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]AlbumRow, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if a, ok := byID[id]; ok {
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ArtistsByIDs — исполнители в порядке ids.
|
||||
func (st *Store) ArtistsByIDs(ctx context.Context, ids []uuid.UUID) ([]Ref, error) {
|
||||
rows, err := st.pool.Query(ctx, `SELECT id, name FROM artists WHERE id = ANY($1)`, ids)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("library: artists: %w", err)
|
||||
}
|
||||
refs, err := pgx.CollectRows(rows, func(r pgx.CollectableRow) (Ref, error) {
|
||||
var a Ref
|
||||
return a, r.Scan(&a.ID, &a.Name)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("library: artists: %w", err)
|
||||
}
|
||||
byID := map[uuid.UUID]Ref{}
|
||||
for _, a := range refs {
|
||||
byID[a.ID] = a
|
||||
}
|
||||
out := make([]Ref, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if a, ok := byID[id]; ok {
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -315,7 +315,8 @@ func TrackDTO(t TrackRow) gen.Track {
|
||||
}
|
||||
}
|
||||
|
||||
func albumSummaryDTO(a AlbumRow) gen.AlbumSummary {
|
||||
// AlbumSummaryDTO переводит альбом в схему AlbumSummary.
|
||||
func AlbumSummaryDTO(a AlbumRow) gen.AlbumSummary {
|
||||
return gen.AlbumSummary{
|
||||
Id: a.ID, Title: a.Title, Year: a.Year, CoverUrl: coverURL(a), CoverColors: colors(a.CoverColors),
|
||||
Artist: gen.ArtistRef{Id: a.Artist.ID, Name: a.Artist.Name}, TrackCount: a.TrackCount,
|
||||
@@ -342,7 +343,7 @@ func (h *Handler) ListArtistAlbums(ctx context.Context, req gen.ListArtistAlbums
|
||||
}
|
||||
out := gen.ListArtistAlbums200JSONResponse{Items: make([]gen.AlbumSummary, 0, len(rows))}
|
||||
for _, a := range rows {
|
||||
out.Items = append(out.Items, albumSummaryDTO(a))
|
||||
out.Items = append(out.Items, AlbumSummaryDTO(a))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ type Service struct {
|
||||
cfg Config
|
||||
log *slog.Logger
|
||||
mu sync.Mutex // сериализует переподключение одного и того же источника
|
||||
|
||||
// OnCatalogChanged — каталог изменился не сканом (удалено хранилище): переиндексация поиска.
|
||||
OnCatalogChanged func()
|
||||
}
|
||||
|
||||
// NewService создаёт сервис.
|
||||
@@ -337,7 +340,13 @@ func (s *Service) DeleteSource(ctx context.Context, id uuid.UUID) error {
|
||||
if err := s.store.DeleteSource(ctx, id); err != nil {
|
||||
return notFound(err)
|
||||
}
|
||||
return s.store.CleanupOrphans(ctx)
|
||||
if err := s.store.CleanupOrphans(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.OnCatalogChanged != nil {
|
||||
s.OnCatalogChanged()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestResult — итог проверки подключения.
|
||||
|
||||
Reference in new issue
Block a user