diff --git a/.env.sample b/.env.sample index 9bac084..7a1a8ba 100644 --- a/.env.sample +++ b/.env.sample @@ -18,8 +18,8 @@ NEXT_PUBLIC_EXCLUSIVE_ARTIST_LABEL=OKI Exclusif # FUNKWHALE VARIABLE NEXT_PUBLIC_OKI_MIZIK_URL=https://funkwhale-server.com -NEXT_PUBLIC_MIZIK_API_USER=user -NEXT_PUBLIC_MIZIK_API_PASSWORD=password +MIZIK_API_USER=user +MIZIK_API_PASSWORD=password NEXT_PUBLIC_AWTIS_POU_CHAK_PAJ=6 NEXT_PUBLIC_SITE_URL=$SITE_URL diff --git a/app/api/mizik-stream/[id]/__tests__/route.test.js b/app/api/mizik-stream/[id]/__tests__/route.test.js new file mode 100644 index 0000000..a5fdc97 --- /dev/null +++ b/app/api/mizik-stream/[id]/__tests__/route.test.js @@ -0,0 +1,55 @@ +import {describe, it, expect, vi, afterEach} from 'vitest' +import {GET} from '../route' + +function fakeUpstreamResponse({status = 200, headers = {}, body = 'audio-bytes'} = {}) { + return { + status, + body, + headers: { + get: key => headers[key.toLowerCase()] ?? null + } + } +} + +describe('GET /api/mizik-stream/[id]', () => { + const originalFetch = global.fetch + + afterEach(() => { + global.fetch = originalFetch + vi.restoreAllMocks() + }) + + it('n\'expose pas les identifiants au client, uniquement l\'id dans l\'URL amont', async () => { + global.fetch = vi.fn(async () => fakeUpstreamResponse()) + + const request = new Request('http://localhost/api/mizik-stream/42') + await GET(request, {params: Promise.resolve({id: '42'})}) + + const [calledUrl] = global.fetch.mock.calls[0] + expect(calledUrl).toContain('id=42') + expect(calledUrl).toMatch(/[?&]u=/) + expect(calledUrl).toMatch(/[?&]p=/) + }) + + it('transmet l\'en-tête Range pour permettre le seek audio', async () => { + global.fetch = vi.fn(async () => fakeUpstreamResponse({status: 206, headers: {'content-range': 'bytes 0-99/200'}})) + + const request = new Request('http://localhost/api/mizik-stream/42', {headers: {range: 'bytes=0-99'}}) + const response = await GET(request, {params: Promise.resolve({id: '42'})}) + + const [, options] = global.fetch.mock.calls[0] + expect(options.headers.range).toBe('bytes=0-99') + expect(response.status).toBe(206) + expect(response.headers.get('content-range')).toBe('bytes 0-99/200') + }) + + it('renvoie le flux et le statut renvoyés par le serveur Funkwhale', async () => { + global.fetch = vi.fn(async () => fakeUpstreamResponse({status: 200, headers: {'content-type': 'audio/mpeg'}})) + + const request = new Request('http://localhost/api/mizik-stream/7') + const response = await GET(request, {params: Promise.resolve({id: '7'})}) + + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toBe('audio/mpeg') + }) +}) diff --git a/app/api/mizik-stream/[id]/route.js b/app/api/mizik-stream/[id]/route.js new file mode 100644 index 0000000..c4d5829 --- /dev/null +++ b/app/api/mizik-stream/[id]/route.js @@ -0,0 +1,29 @@ +const MIZIK_URL = process.env.NEXT_PUBLIC_OKI_MIZIK_URL || 'https://funkwhale-server.com' +const MIZIK_API_USER = process.env.MIZIK_API_USER || 'user' +const MIZIK_API_PASSWORD = process.env.MIZIK_API_PASSWORD || 'password' + +const FORWARDED_HEADERS = ['content-type', 'content-length', 'content-range', 'accept-ranges'] + +export async function GET(request, props) { + const {id} = await props.params + + const upstreamUrl = `${MIZIK_URL}/rest/stream?u=${encodeURIComponent(MIZIK_API_USER)}&p=${encodeURIComponent(MIZIK_API_PASSWORD)}&id=${encodeURIComponent(id)}` + const range = request.headers.get('range') + + const upstreamResponse = await fetch(upstreamUrl, { + headers: range ? {range} : {} + }) + + const headers = new Headers() + for (const key of FORWARDED_HEADERS) { + const value = upstreamResponse.headers.get(key) + if (value) { + headers.set(key, value) + } + } + + return new Response(upstreamResponse.body, { + status: upstreamResponse.status, + headers + }) +} diff --git a/components/teks/oki-mizik.js b/components/teks/oki-mizik.js index 756329d..f769122 100644 --- a/components/teks/oki-mizik.js +++ b/components/teks/oki-mizik.js @@ -3,8 +3,6 @@ import {Box, CircularProgress} from '@mui/material' import dynamic from 'next/dynamic' const MIZIK_URL = process.env.NEXT_PUBLIC_OKI_MIZIK_URL || 'https://funkwhale-server.com' -const MIZIK_API_USER = process.env.NEXT_PUBLIC_MIZIK_API_USER || 'user' -const MIZIK_API_PASSWORD = process.env.NEXT_PUBLIC_MIZIK_API_PASSWORD || 'password' const DinamikLekte = dynamic( () => import('./lekte'), // eslint-disable-line node/no-unsupported-features/es-syntax @@ -12,7 +10,7 @@ const DinamikLekte = dynamic( ) export default function OkiMizik({id, parole}) { - const mizikStreamUrl = `${MIZIK_URL}/rest/stream?u=${MIZIK_API_USER}&p=${MIZIK_API_PASSWORD}&id=${id}` + const mizikStreamUrl = `/api/mizik-stream/${id}` const detailsUrl = `${MIZIK_URL}/library/tracks/${id}` return (