Compare commits
3 Commits
0fbfb6f8ed
...
f05958ed36
| Author | SHA1 | Date | |
|---|---|---|---|
| f05958ed36 | |||
| aa252487b8 | |||
| ed7034f55b |
@@ -1,13 +1,15 @@
|
||||
"""Audio streaming and download endpoints."""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Query
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from uuid import UUID
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from ...models.database import get_db
|
||||
from ...models import crud
|
||||
from ...core.waveform_generator import get_waveform_data
|
||||
from ...core.auth import verify_token
|
||||
from ...utils.logging import get_logger
|
||||
|
||||
router = APIRouter()
|
||||
@@ -18,6 +20,7 @@ logger = get_logger(__name__)
|
||||
async def stream_audio(
|
||||
track_id: UUID,
|
||||
request: Request,
|
||||
token: Optional[str] = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Stream audio file with range request support.
|
||||
@@ -28,6 +31,7 @@ async def stream_audio(
|
||||
Args:
|
||||
track_id: Track UUID
|
||||
request: HTTP request
|
||||
token: Optional JWT token for authentication (for <audio> tag compatibility)
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
@@ -36,6 +40,9 @@ async def stream_audio(
|
||||
Raises:
|
||||
HTTPException: 404 if track not found or file doesn't exist
|
||||
"""
|
||||
# Verify authentication via query parameter for <audio> tag
|
||||
if token:
|
||||
verify_token(token)
|
||||
track = crud.get_track_by_id(db, track_id)
|
||||
|
||||
if not track:
|
||||
@@ -79,12 +86,14 @@ async def stream_audio(
|
||||
@router.get("/download/{track_id}")
|
||||
async def download_audio(
|
||||
track_id: UUID,
|
||||
token: Optional[str] = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Download audio file.
|
||||
|
||||
Args:
|
||||
track_id: Track UUID
|
||||
token: Optional JWT token for authentication (for <a> tag compatibility)
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
@@ -93,6 +102,9 @@ async def download_audio(
|
||||
Raises:
|
||||
HTTPException: 404 if track not found or file doesn't exist
|
||||
"""
|
||||
# Verify authentication via query parameter for <a> tag
|
||||
if token:
|
||||
verify_token(token)
|
||||
track = crud.get_track_by_id(db, track_id)
|
||||
|
||||
if not track:
|
||||
|
||||
@@ -100,22 +100,13 @@ def authenticate_user(email: str, password: str) -> Optional[dict]:
|
||||
Returns:
|
||||
User data if authenticated, None otherwise
|
||||
"""
|
||||
# Debug logging (remove in production)
|
||||
logger.info(f"Auth attempt - Email provided: '{email}'")
|
||||
logger.info(f"Auth attempt - Expected email: '{settings.ADMIN_EMAIL}'")
|
||||
logger.info(f"Auth attempt - Email match: {email == settings.ADMIN_EMAIL}")
|
||||
logger.info(f"Auth attempt - Password length: {len(password)}")
|
||||
logger.info(f"Auth attempt - Expected password length: {len(settings.ADMIN_PASSWORD)}")
|
||||
|
||||
# Check against admin credentials from environment
|
||||
if email == settings.ADMIN_EMAIL and password == settings.ADMIN_PASSWORD:
|
||||
logger.info(f"✅ Authentication successful for {email}")
|
||||
return {
|
||||
"email": email,
|
||||
"role": "admin"
|
||||
}
|
||||
|
||||
logger.warning(f"❌ Authentication failed for {email}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@ services:
|
||||
ANALYSIS_USE_CLAP: ${ANALYSIS_USE_CLAP:-false}
|
||||
ANALYSIS_NUM_WORKERS: ${ANALYSIS_NUM_WORKERS:-4}
|
||||
ESSENTIA_MODELS_PATH: /app/models
|
||||
# Authentication
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@example.com}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-changeme}
|
||||
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-your-secret-key-change-in-production}
|
||||
JWT_EXPIRATION_HOURS: ${JWT_EXPIRATION_HOURS:-24}
|
||||
ports:
|
||||
- "8001:8000"
|
||||
volumes:
|
||||
|
||||
@@ -91,8 +91,17 @@ export default function Home() {
|
||||
setIsScanning(true)
|
||||
setScanStatus("Démarrage du scan...")
|
||||
|
||||
const token = localStorage.getItem('access_token')
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const response = await fetch(`${getApiUrl()}/api/library/scan`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -104,7 +113,9 @@ export default function Home() {
|
||||
// Poll scan status
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const statusResponse = await fetch(`${getApiUrl()}/api/library/scan/status`)
|
||||
const statusResponse = await fetch(`${getApiUrl()}/api/library/scan/status`, {
|
||||
headers,
|
||||
})
|
||||
const status = await statusResponse.json()
|
||||
|
||||
if (!status.is_scanning) {
|
||||
|
||||
@@ -79,8 +79,15 @@ export default function AudioPlayer({ track, isPlaying, onPlayingChange }: Audio
|
||||
const loadWaveform = async (trackId: string) => {
|
||||
setIsLoadingWaveform(true)
|
||||
try {
|
||||
const token = localStorage.getItem('access_token')
|
||||
const headers: HeadersInit = {}
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${getApiUrl()}/api/audio/waveform/${trackId}`
|
||||
`${getApiUrl()}/api/audio/waveform/${trackId}`,
|
||||
{ headers }
|
||||
)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
@@ -159,10 +166,19 @@ export default function AudioPlayer({ track, isPlaying, onPlayingChange }: Audio
|
||||
|
||||
const progress = duration > 0 ? (currentTime / duration) * 100 : 0
|
||||
|
||||
const getAuthenticatedStreamUrl = (trackId: string) => {
|
||||
const token = localStorage.getItem('access_token')
|
||||
const baseUrl = `${getApiUrl()}/api/audio/stream/${trackId}`
|
||||
if (token) {
|
||||
return `${baseUrl}?token=${encodeURIComponent(token)}`
|
||||
}
|
||||
return baseUrl
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 border-t border-gray-300 shadow-lg" style={{ height: '80px' }}>
|
||||
{/* Hidden audio element */}
|
||||
{track && <audio ref={audioRef} src={`${getApiUrl()}/api/audio/stream/${track.id}`} />}
|
||||
{track && <audio ref={audioRef} src={getAuthenticatedStreamUrl(track.id)} />}
|
||||
|
||||
<div className="h-full flex items-center gap-3 px-4">
|
||||
{/* Play/Pause button */}
|
||||
@@ -301,7 +317,7 @@ export default function AudioPlayer({ track, isPlaying, onPlayingChange }: Audio
|
||||
{/* Download button */}
|
||||
{track && (
|
||||
<a
|
||||
href={`${getApiUrl()}/api/audio/download/${track.id}`}
|
||||
href={getAuthenticatedStreamUrl(track.id).replace('/stream/', '/download/')}
|
||||
download
|
||||
className="w-8 h-8 flex items-center justify-center text-gray-600 hover:text-gray-900 transition-colors rounded hover:bg-gray-200 flex-shrink-0"
|
||||
aria-label="Download"
|
||||
|
||||
Reference in New Issue
Block a user