diff --git a/.cursor/rules/api-conventions.mdc b/.cursor/rules/api-conventions.mdc new file mode 100644 index 0000000..0f76edf --- /dev/null +++ b/.cursor/rules/api-conventions.mdc @@ -0,0 +1,32 @@ +--- +description: REST API запросы — создавать функции в furumiApi.ts (только furumi-node-player/client) +globs: furumi-node-player/client/**/*.{ts,tsx} +alwaysApply: false +--- + +# REST API в furumi-node-player + +**Область действия:** правило применяется только к проекту `furumi-node-player/client/`. В остальных частях репозитория можно использовать другой подход. + +Чтобы выполнить REST API запрос, нужно создать функцию, которая принимает необходимые параметры и вызывает `furumiApi`. Все такие функции должны находиться в файле `furumi-node-player/client/src/furumiApi.ts`. + +## Правила + +1. **Не вызывать `furumiApi` напрямую** из компонентов или других модулей. +2. **Добавлять новую функцию** в `furumiApi.ts` для каждого эндпоинта. +3. Функция принимает нужные параметры и возвращает `Promise` с данными (или `null` при ошибке). + +## Пример + +```typescript +// furumiApi.ts — добавлять сюда +export async function getSomething(id: string) { + const res = await furumiApi.get(`/something/${id}`).catch(() => null) + return res?.data ?? null +} +``` + +```typescript +// FurumiPlayer.tsx — использовать функцию, не furumiApi напрямую +const data = await getSomething(id) +``` diff --git a/furumi-node-player/client/src/FurumiPlayer.tsx b/furumi-node-player/client/src/FurumiPlayer.tsx index fda0363..297eab9 100644 --- a/furumi-node-player/client/src/FurumiPlayer.tsx +++ b/furumi-node-player/client/src/FurumiPlayer.tsx @@ -1,6 +1,15 @@ import { useEffect, useRef, useState, type MouseEvent as ReactMouseEvent } from 'react' import './furumi-player.css' -import { API_ROOT, furumiApi } from './furumiApi' +import { + API_ROOT, + getArtists, + getArtistAlbums, + getAlbumTracks, + getArtistTracks, + searchTracks, + getTrackInfo, + preloadStream, +} from './furumiApi' import { SearchDropdown } from './components/SearchDropdown' import { Breadcrumbs } from './components/Breadcrumbs' import { LibraryList } from './components/LibraryList' @@ -107,7 +116,7 @@ export function FurumiPlayer() { setBreadcrumb([{ label: 'Artists', action: showArtists }]) setLibraryLoading(true) setLibraryError(null) - const artists = (await furumiApi.get('/artists').catch(() => null))?.data ?? null + const artists = await getArtists() if (!artists) { setLibraryLoading(false) setLibraryError('Error') @@ -133,7 +142,7 @@ export function FurumiPlayer() { ]) setLibraryLoading(true) setLibraryError(null) - const albums = (await furumiApi.get('/artists/' + artistSlug + '/albums').catch(() => null))?.data ?? null + const albums = await getArtistAlbums(artistSlug) if (!albums) { setLibraryLoading(false) setLibraryError('Error') @@ -182,7 +191,7 @@ export function FurumiPlayer() { ]) setLibraryLoading(true) setLibraryError(null) - const tracks = (await furumiApi.get('/albums/' + albumSlug).catch(() => null))?.data ?? null + const tracks = await getAlbumTracks(albumSlug) if (!tracks) { setLibraryLoading(false) setLibraryError('Error') @@ -252,7 +261,7 @@ export function FurumiPlayer() { } async function addAlbumToQueue(albumSlug: string, playFirst?: boolean) { - const tracks = (await furumiApi.get('/albums/' + albumSlug).catch(() => null))?.data ?? null + const tracks = await getAlbumTracks(albumSlug) if (!tracks || !(tracks as any[]).length) return const list = tracks as any[] let firstIdx = queue.length @@ -272,7 +281,7 @@ export function FurumiPlayer() { } async function playAllArtistTracks(artistSlug: string) { - const tracks = (await furumiApi.get('/artists/' + artistSlug + '/tracks').catch(() => null))?.data ?? null + const tracks = await getArtistTracks(artistSlug) if (!tracks || !(tracks as any[]).length) return const list = tracks as any[] clearQueue() @@ -487,7 +496,7 @@ export function FurumiPlayer() { return } searchTimer = window.setTimeout(async () => { - const results = (await furumiApi.get('/search?q=' + encodeURIComponent(q)).catch(() => null))?.data ?? null + const results = await searchTracks(q) if (!results || !(results as any[]).length) { closeSearch() return @@ -511,7 +520,7 @@ export function FurumiPlayer() { { slug, title: '', artist: '', album_slug: null, duration: null }, true, ) - void furumiApi.get('/stream/' + slug).catch(() => null) + void preloadStream(slug) } } searchSelectRef.current = onSearchSelect @@ -617,7 +626,7 @@ export function FurumiPlayer() { const url = new URL(window.location.href) const urlSlug = url.searchParams.get('t') if (urlSlug) { - const info = (await furumiApi.get('/tracks/' + urlSlug).catch(() => null))?.data ?? null + const info = await getTrackInfo(urlSlug) if (info) { addTrackToQueue( { diff --git a/furumi-node-player/client/src/furumiApi.ts b/furumi-node-player/client/src/furumiApi.ts index 8cc8c4e..38abe12 100644 --- a/furumi-node-player/client/src/furumiApi.ts +++ b/furumi-node-player/client/src/furumiApi.ts @@ -10,3 +10,37 @@ export const furumiApi = axios.create({ headers: apiKey ? { 'x-api-key': apiKey } : {}, }) +export async function getArtists() { + const res = await furumiApi.get('/artists').catch(() => null) + return res?.data ?? null +} + +export async function getArtistAlbums(artistSlug: string) { + const res = await furumiApi.get(`/artists/${artistSlug}/albums`).catch(() => null) + return res?.data ?? null +} + +export async function getAlbumTracks(albumSlug: string) { + const res = await furumiApi.get(`/albums/${albumSlug}`).catch(() => null) + return res?.data ?? null +} + +export async function getArtistTracks(artistSlug: string) { + const res = await furumiApi.get(`/artists/${artistSlug}/tracks`).catch(() => null) + return res?.data ?? null +} + +export async function searchTracks(query: string) { + const res = await furumiApi.get(`/search?q=${encodeURIComponent(query)}`).catch(() => null) + return res?.data ?? null +} + +export async function getTrackInfo(trackSlug: string) { + const res = await furumiApi.get(`/tracks/${trackSlug}`).catch(() => null) + return res?.data ?? null +} + +export async function preloadStream(trackSlug: string) { + await furumiApi.get(`/stream/${trackSlug}`).catch(() => null) +} +