From 31c6fd3265780d126c1367b75395d225d0909d95 Mon Sep 17 00:00:00 2001 From: Mark Hoekveen Date: Sat, 19 Jul 2025 19:53:24 +0200 Subject: [PATCH] adds track loading example --- backend/playlist/models/track.py | 5 ++ backend/playlist/serializers/track.py | 5 +- backend/playlist/urls.py | 1 + backend/playlist/views/api/track.py | 14 ++++++ frontend/next.config.js | 7 +++ frontend/src/app/voting/page.tsx | 70 +++++++++++++++++++-------- frontend/src/data/fetcher.ts | 1 + 7 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 frontend/next.config.js diff --git a/backend/playlist/models/track.py b/backend/playlist/models/track.py index 8f0bc7a..764feda 100644 --- a/backend/playlist/models/track.py +++ b/backend/playlist/models/track.py @@ -104,6 +104,11 @@ class Track(models.Model): artists = list(map(lambda a:a.name, self.artists.all())) return " & ".join(artists) + @property + def album_cover(self): + if self.album: + return self.album.image_url + @property def name_sanitized(self): return re.match("[^\-\(]*\w", self.name)[0] diff --git a/backend/playlist/serializers/track.py b/backend/playlist/serializers/track.py index a985026..5fb642d 100644 --- a/backend/playlist/serializers/track.py +++ b/backend/playlist/serializers/track.py @@ -47,6 +47,9 @@ from drf_spectacular.utils import extend_schema_serializer, OpenApiExample ] ) class TrackSerializer(serializers.HyperlinkedModelSerializer): + artist = serializers.CharField() + album_cover = serializers.CharField() + class Meta: model = Track fields = '__all__' @@ -54,5 +57,5 @@ class TrackSerializer(serializers.HyperlinkedModelSerializer): 'url': {'view_name': 'api-track', 'lookup_field': 'id'}, 'artists': {'view_name': 'api-artist', 'lookup_field': 'id'}, 'album': {'view_name': 'api-album', 'lookup_field': 'id'}, - 'nominated_by': {'view_name': 'api-user', 'lookup_field': 'id'} + 'nominated_by': {'view_name': 'api-user', 'lookup_field': 'id'}, } diff --git a/backend/playlist/urls.py b/backend/playlist/urls.py index bda1144..6a3589c 100644 --- a/backend/playlist/urls.py +++ b/backend/playlist/urls.py @@ -10,6 +10,7 @@ urlpatterns = [ path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/track/', views.TrackListView.as_view(), name="api-tracks"), + path('api/track/vote', views.TrackVoteView.as_view(), name="api-track-vote"), path('api/track/', views.TrackDetailView.as_view(), name="api-track"), path('api/artist/', views.ArtistDetailView.as_view(), name="api-artist"), path('api/artist', views.ArtistListView.as_view(), name="api-artists"), diff --git a/backend/playlist/views/api/track.py b/backend/playlist/views/api/track.py index b3c11fd..40d22c6 100644 --- a/backend/playlist/views/api/track.py +++ b/backend/playlist/views/api/track.py @@ -5,6 +5,7 @@ from playlist.serializers import TrackSerializer from playlist.spotify import spt from playlist.tasks import get_banter, get_and_dither_image from drf_spectacular.utils import extend_schema +from random import choice class TrackListView(APIView): @extend_schema( @@ -64,3 +65,16 @@ class TrackDetailView(APIView): return Response(status=204) else: return Response({'error': 'Only superusers can delete tracks'}, status=403) + +class TrackVoteView(APIView): + @extend_schema( + request=None, + responses={200: TrackSerializer}, + description="Retrieve a random track for voting." + ) + def get(self, request): + # TODO implement a session-based vote tracking system. Random return for now. + random_pk = choice(Track.objects.values_list('pk', flat=True)) + track = Track.objects.get(pk=random_pk) + serializer = TrackSerializer(track, context={'request': request}) + return Response(serializer.data) diff --git a/frontend/next.config.js b/frontend/next.config.js new file mode 100644 index 0000000..ea86c2b --- /dev/null +++ b/frontend/next.config.js @@ -0,0 +1,7 @@ +const nextConfig = { + images: { + remotePatterns: [new URL("https://i.scdn.co/**")], + }, +}; + +export default nextConfig; diff --git a/frontend/src/app/voting/page.tsx b/frontend/src/app/voting/page.tsx index 009864d..6e9a36a 100644 --- a/frontend/src/app/voting/page.tsx +++ b/frontend/src/app/voting/page.tsx @@ -4,6 +4,7 @@ import { Nunito } from "next/font/google"; import users from "muzak/data/users.json"; import Image from "next/image"; import Link from "next/link"; +import { api, Track } from "muzak/data/fetcher"; const nunito = Nunito({ weight: "900", @@ -16,15 +17,25 @@ export default function Lobby() { const [yesVoteList, setYesVoteList] = useState([]); const [noVoteList, setNoVoteList] = useState([]); - const [currentSong, setCurrentSong] = useState({ - artist: "The Meowls", - title: "The Owls Are Not What They Seem", - }); + const [currentSong, setCurrentSong] = useState(); + const [songLoading, setSongLoading] = useState(false); useEffect(() => { populateUserList(); }, [partySize]); + async function getTrack() { + setSongLoading(true); + const { data: track } = await api.tracks.vote({}); + setCurrentSong(track); + console.log("I got a track!", track.name); + setSongLoading(false); + } + + useEffect(() => { + if (!currentSong && !songLoading) getTrack(); + }, []); + function populateUserList() { const mappedUsers = users.slice(0, partySize).map((user) => ({ id: user.id, @@ -50,7 +61,7 @@ export default function Lobby() {
- 30 +
@@ -86,10 +97,25 @@ export default function Lobby() { ))}{" "} - -
- meowl -
+ {!songLoading && currentSong ? ( +
+ {currentSong.name} +
+ ) : ( +
+ Loading.. +
+ )}
{userList.map((user) => (
-
-

- {currentSong.artist} -

-

- {currentSong.title} -

-
+ {!songLoading && currentSong && ( +
+

+ {currentSong.artist} +

+

+ {currentSong.name} +

+
+ )}
diff --git a/frontend/src/data/fetcher.ts b/frontend/src/data/fetcher.ts index e6dd53e..e0f7f65 100644 --- a/frontend/src/data/fetcher.ts +++ b/frontend/src/data/fetcher.ts @@ -49,6 +49,7 @@ export const api = { tracks: { list: fetcher.path("/api/track/").method("get").create(), detail: fetcher.path("/api/track/{id}").method("get").create(), + vote: fetcher.path("/api/track/vote").method("get").create(), }, artists: { list: fetcher.path("/api/artist").method("get").create(),