feat: Add cover art support and auto-migration

- Extract cover art from MP3s during upload
- Display cover art in game result screens (win/loss)
- Add coverImage field to Song model
- Add migration script to backfill covers for existing songs
- Configure Docker to run migration script on startup
This commit is contained in:
Hördle Bot
2025-11-21 15:51:22 +01:00
parent 0c9508076f
commit 29d43effe3
8 changed files with 178 additions and 4 deletions

View File

@@ -35,6 +35,10 @@ export default function AdminPage() {
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10;
// Audio state
const [playingSongId, setPlayingSongId] = useState<number | null>(null);
const [audioElement, setAudioElement] = useState<HTMLAudioElement | null>(null);
const handleLogin = async () => {
const res = await fetch('/api/admin/login', {
method: 'POST',
@@ -132,6 +136,29 @@ export default function AdminPage() {
}
};
const handlePlayPause = (song: Song) => {
if (playingSongId === song.id) {
// Pause current song
audioElement?.pause();
setPlayingSongId(null);
} else {
// Stop any currently playing song
audioElement?.pause();
// Play new song
const audio = new Audio(`/uploads/${song.filename}`);
audio.play();
setAudioElement(audio);
setPlayingSongId(song.id);
// Reset when song ends
audio.onended = () => {
setPlayingSongId(null);
setAudioElement(null);
};
}
};
// Filter and sort songs
const filteredSongs = songs.filter(song =>
song.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
@@ -297,6 +324,13 @@ export default function AdminPage() {
<td style={{ padding: '0.75rem', color: '#666' }}>{song.activations}</td>
<td style={{ padding: '0.75rem' }}>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button
onClick={() => handlePlayPause(song)}
style={{ fontSize: '1.25rem', cursor: 'pointer', border: 'none', background: 'none' }}
title={playingSongId === song.id ? "Pause" : "Play"}
>
{playingSongId === song.id ? '⏸️' : '▶️'}
</button>
<button
onClick={() => startEditing(song)}
style={{ fontSize: '1.25rem', cursor: 'pointer', border: 'none', background: 'none' }}