Merge pull request 'Fix CORS' (#3) from prochains-changements into main
All checks were successful
Build and Push Docker Images / Build Backend Image (push) Successful in 12m50s
Build and Push Docker Images / Build Frontend Image (push) Successful in 7m10s

Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
2025-12-23 14:34:05 +01:00
6 changed files with 59 additions and 25 deletions

View File

@@ -5,7 +5,9 @@ POSTGRES_PASSWORD=audio_password
POSTGRES_DB=audio_classifier POSTGRES_DB=audio_classifier
# Backend API # Backend API
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 # Use "*" to allow all origins (recommended for development/local deployment)
# Or specify comma-separated URLs for production: http://yourdomain.com,https://yourdomain.com
CORS_ORIGINS=*
API_HOST=0.0.0.0 API_HOST=0.0.0.0
API_PORT=8000 API_PORT=8000
@@ -16,4 +18,5 @@ ESSENTIA_MODELS_PATH=/app/models
AUDIO_LIBRARY_PATH=/path/to/your/audio/library AUDIO_LIBRARY_PATH=/path/to/your/audio/library
# Frontend # Frontend
NEXT_PUBLIC_API_URL=http://localhost:8000 # API URL accessed by the browser (use port 8001 since backend is mapped to 8001)
NEXT_PUBLIC_API_URL=http://localhost:8001

View File

@@ -10,7 +10,8 @@ class Settings(BaseSettings):
DATABASE_URL: str = "postgresql://audio_user:audio_password@localhost:5432/audio_classifier" DATABASE_URL: str = "postgresql://audio_user:audio_password@localhost:5432/audio_classifier"
# API Configuration # API Configuration
CORS_ORIGINS: str = "http://localhost:3000,http://127.0.0.1:3000" # Comma-separated list of allowed origins, or use "*" to allow all
CORS_ORIGINS: str = "*"
API_HOST: str = "0.0.0.0" API_HOST: str = "0.0.0.0"
API_PORT: int = 8000 API_PORT: int = 8000
@@ -33,7 +34,13 @@ class Settings(BaseSettings):
@property @property
def cors_origins_list(self) -> List[str]: def cors_origins_list(self) -> List[str]:
"""Parse CORS origins string to list.""" """Parse CORS origins string to list.
If CORS_ORIGINS is "*", allow all origins.
Otherwise, parse comma-separated list.
"""
if self.CORS_ORIGINS.strip() == "*":
return ["*"]
return [origin.strip() for origin in self.CORS_ORIGINS.split(",")] return [origin.strip() for origin in self.CORS_ORIGINS.split(",")]

View File

@@ -31,7 +31,7 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-audio_user}:${POSTGRES_PASSWORD:-audio_password}@postgres:5432/${POSTGRES_DB:-audio_classifier} DATABASE_URL: postgresql://${POSTGRES_USER:-audio_user}:${POSTGRES_PASSWORD:-audio_password}@postgres:5432/${POSTGRES_DB:-audio_classifier}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000} CORS_ORIGINS: ${CORS_ORIGINS:-*}
ANALYSIS_USE_CLAP: ${ANALYSIS_USE_CLAP:-false} ANALYSIS_USE_CLAP: ${ANALYSIS_USE_CLAP:-false}
ANALYSIS_NUM_WORKERS: ${ANALYSIS_NUM_WORKERS:-4} ANALYSIS_NUM_WORKERS: ${ANALYSIS_NUM_WORKERS:-4}
ESSENTIA_MODELS_PATH: /app/models ESSENTIA_MODELS_PATH: /app/models

View File

@@ -26,7 +26,7 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-audio_user}:${POSTGRES_PASSWORD:-audio_password}@postgres:5432/${POSTGRES_DB:-audio_classifier} DATABASE_URL: postgresql://${POSTGRES_USER:-audio_user}:${POSTGRES_PASSWORD:-audio_password}@postgres:5432/${POSTGRES_DB:-audio_classifier}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000} CORS_ORIGINS: ${CORS_ORIGINS:-*}
ANALYSIS_USE_CLAP: ${ANALYSIS_USE_CLAP:-false} ANALYSIS_USE_CLAP: ${ANALYSIS_USE_CLAP:-false}
ANALYSIS_NUM_WORKERS: ${ANALYSIS_NUM_WORKERS:-4} ANALYSIS_NUM_WORKERS: ${ANALYSIS_NUM_WORKERS:-4}
ESSENTIA_MODELS_PATH: /app/models ESSENTIA_MODELS_PATH: /app/models

View File

@@ -52,6 +52,7 @@ export default function Home() {
const [filters, setFilters] = useState<FilterParams>({}) const [filters, setFilters] = useState<FilterParams>({})
const [page, setPage] = useState(0) const [page, setPage] = useState(0)
const [currentTrack, setCurrentTrack] = useState<Track | null>(null) const [currentTrack, setCurrentTrack] = useState<Track | null>(null)
const [isPlaying, setIsPlaying] = useState(false)
const [searchQuery, setSearchQuery] = useState("") const [searchQuery, setSearchQuery] = useState("")
const [isScanning, setIsScanning] = useState(false) const [isScanning, setIsScanning] = useState(false)
const [scanStatus, setScanStatus] = useState<string>("") const [scanStatus, setScanStatus] = useState<string>("")
@@ -233,10 +234,19 @@ export default function Home() {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{/* Play button */} {/* Play button */}
<button <button
onClick={() => setCurrentTrack(track)} onClick={() => {
if (currentTrack?.id === track.id) {
// Toggle play/pause for current track
setIsPlaying(!isPlaying)
} else {
// Switch to new track and start playing
setCurrentTrack(track)
setIsPlaying(true)
}
}}
className="flex-shrink-0 w-12 h-12 flex items-center justify-center bg-orange-500 hover:bg-orange-600 rounded-full transition-colors shadow-sm" className="flex-shrink-0 w-12 h-12 flex items-center justify-center bg-orange-500 hover:bg-orange-600 rounded-full transition-colors shadow-sm"
> >
{currentTrack?.id === track.id ? ( {currentTrack?.id === track.id && isPlaying ? (
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/> <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>
</svg> </svg>
@@ -347,7 +357,11 @@ export default function Home() {
{/* Fixed Audio Player at bottom */} {/* Fixed Audio Player at bottom */}
<div className="fixed bottom-0 left-0 right-0 z-50"> <div className="fixed bottom-0 left-0 right-0 z-50">
<AudioPlayer track={currentTrack} /> <AudioPlayer
track={currentTrack}
isPlaying={isPlaying}
onPlayingChange={setIsPlaying}
/>
</div> </div>
</div> </div>
) )

View File

@@ -5,10 +5,11 @@ import type { Track } from "@/lib/types"
interface AudioPlayerProps { interface AudioPlayerProps {
track: Track | null track: Track | null
isPlaying: boolean
onPlayingChange: (playing: boolean) => void
} }
export default function AudioPlayer({ track }: AudioPlayerProps) { export default function AudioPlayer({ track, isPlaying, onPlayingChange }: AudioPlayerProps) {
const [isPlaying, setIsPlaying] = useState(false)
const [currentTime, setCurrentTime] = useState(0) const [currentTime, setCurrentTime] = useState(0)
const [duration, setDuration] = useState(0) const [duration, setDuration] = useState(0)
const [volume, setVolume] = useState(1) const [volume, setVolume] = useState(1)
@@ -22,7 +23,7 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
// Load audio and waveform when track changes // Load audio and waveform when track changes
useEffect(() => { useEffect(() => {
if (!track) { if (!track) {
setIsPlaying(false) onPlayingChange(false)
setCurrentTime(0) setCurrentTime(0)
setWaveformPeaks([]) setWaveformPeaks([])
return return
@@ -33,13 +34,13 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
if (audioRef.current) { if (audioRef.current) {
audioRef.current.load() audioRef.current.load()
// Autoplay when track loads // Autoplay when track loads if isPlaying is true
audioRef.current.play().then(() => { if (isPlaying) {
setIsPlaying(true) audioRef.current.play().catch((error: unknown) => {
}).catch((error: unknown) => { console.error("Autoplay failed:", error)
console.error("Autoplay failed:", error) onPlayingChange(false)
setIsPlaying(false) })
}) }
} }
}, [track?.id]) }, [track?.id])
@@ -54,7 +55,7 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
setDuration(audio.duration) setDuration(audio.duration)
} }
} }
const handleEnded = () => setIsPlaying(false) const handleEnded = () => onPlayingChange(false)
audio.addEventListener("timeupdate", updateTime) audio.addEventListener("timeupdate", updateTime)
audio.addEventListener("loadedmetadata", updateDuration) audio.addEventListener("loadedmetadata", updateDuration)
@@ -91,15 +92,24 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
} }
} }
const togglePlay = () => { // Sync playing state with audio element
if (!audioRef.current || !track) return useEffect(() => {
const audio = audioRef.current
if (!audio) return
if (isPlaying) { if (isPlaying) {
audioRef.current.pause() audio.play().catch((error: unknown) => {
console.error("Play failed:", error)
onPlayingChange(false)
})
} else { } else {
audioRef.current.play() audio.pause()
} }
setIsPlaying(!isPlaying) }, [isPlaying, onPlayingChange])
const togglePlay = () => {
if (!audioRef.current || !track) return
onPlayingChange(!isPlaying)
} }
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {