From c7eb174a0155637151924710468e77c75ab87e15 Mon Sep 17 00:00:00 2001 From: Mark Hoekveen Date: Sat, 16 Aug 2025 09:51:29 +0200 Subject: [PATCH] betere sessies lol lmao even --- ...session_seed_alter_vote_unique_together.py | 24 ++ backend/voting/consumers.py | 49 ++- backend/voting/urls.py | 1 + backend/voting/views.py | 9 + frontend/src/app/lobby/page.tsx | 328 +++++++++++++----- frontend/src/app/mobile-voting/page.tsx | 15 + frontend/src/data/fetcher.ts | 15 +- 7 files changed, 331 insertions(+), 110 deletions(-) create mode 100644 backend/playlist/migrations/0027_alter_session_seed_alter_vote_unique_together.py diff --git a/backend/playlist/migrations/0027_alter_session_seed_alter_vote_unique_together.py b/backend/playlist/migrations/0027_alter_session_seed_alter_vote_unique_together.py new file mode 100644 index 0000000..937b25c --- /dev/null +++ b/backend/playlist/migrations/0027_alter_session_seed_alter_vote_unique_together.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.4 on 2025-08-15 19:14 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("playlist", "0026_alter_session_players_connected_alter_session_seed"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name="session", + name="seed", + field=models.IntegerField(default=406213), + ), + migrations.AlterUniqueTogether( + name="vote", + unique_together={("user", "track", "session")}, + ), + ] diff --git a/backend/voting/consumers.py b/backend/voting/consumers.py index 1dd1110..2ebd9cf 100644 --- a/backend/voting/consumers.py +++ b/backend/voting/consumers.py @@ -6,6 +6,7 @@ from django.contrib.auth.models import User from muzak.auth import get_user_from_token from channels.db import database_sync_to_async from playlist.models import Session, Track, Vote +import random class SessionConsumer(WebsocketConsumer): @@ -27,6 +28,9 @@ class SessionConsumer(WebsocketConsumer): # Receive message from WebSocket def receive(self, text_data): user = self.scope["user"] # Waarschijnlijk altijd AnonymousUser + print(self.scope) + sessionId = self.scope["url_route"].get("kwargs", {}).get("session_id", None) + print() text_data_json = json.loads(text_data) @@ -35,12 +39,9 @@ class SessionConsumer(WebsocketConsumer): if token: user = get_user_from_token(token, log=True) print(user) - + session = Session.objects.get(session_id=sessionId) action = text_data_json.get("action", None) - #:w - # sessionId = text_data_json.get("sessionId", None) - #session = Session.objects.get(id=sessionId) if action == "message": message = text_data_json["message"] # Send message to room group @@ -52,26 +53,44 @@ class SessionConsumer(WebsocketConsumer): async_to_sync(self.channel_layer.group_send)( self.session_name, {"type": "announce", "user": str(user)} ) - # if action == "vote": - # points = text_data_json.get("points", None) - # track = session.current_track; - # try: - # vote = Vote.objects.get(user=user, track=track, session=session) - # vote.points = points - # vote.save() - # except Vote.DoesNotExist: - # vote = Vote.objects.create(user=user, track=track, points=points, session=session) - # vote.save() + if action == "vote": + points = text_data_json.get("points", None) + track = session.current_track; + try: + vote = Vote.objects.get(user=user, track=track, session=session) + vote.points = points + vote.save() + except Vote.DoesNotExist: + vote = Vote.objects.create(user=user, track=track, points=points, session=session) + vote.save() if action == "pause": pass if action == "next": - pass + random.seed(session.seed) + tracks = Track.objects.order_by("?") + next = False + if session.current_track: + for track in tracks: + if next: + session.current_track = track + break + if session.current_track == track: + next = True + else: + session.current_track = tracks.first() + session.save() + async_to_sync(self.channel_layer.group_send)( + self.session_name, {"type": "announce.track"} + ) def announce(self, event): user = event["user"] self.send(text_data=json.dumps({"joined": user})) + def announce_track(self, event): + self.send(text_data=json.dumps({"track": {}})) + # Receive message from room group def chat_message(self, event): diff --git a/backend/voting/urls.py b/backend/voting/urls.py index 562721a..a3ec181 100644 --- a/backend/voting/urls.py +++ b/backend/voting/urls.py @@ -6,4 +6,5 @@ urlpatterns = [ path("", views.index, name="index"), path("session//test", views.session, name="session"), path("session/", views.SessionListView.as_view(), name="api-session-list"), + path("session//", views.SessionDetailView.as_view(), name="api-session-detail"), ] diff --git a/backend/voting/views.py b/backend/voting/views.py index f684a43..4b8619a 100644 --- a/backend/voting/views.py +++ b/backend/voting/views.py @@ -22,3 +22,12 @@ class SessionListView(APIView): else: sessions = Session.objects.filter(voting_open=True) return Response(SessionSerializer(sessions, many=True).data) + +@extend_schema( + responses={200: SessionSerializer()}, + description="Gets session details." +) +class SessionDetailView(APIView): + def get(self, request, session_id): + session = Session.objects.get(session_id=session_id) + return Response(SessionSerializer(session).data) diff --git a/frontend/src/app/lobby/page.tsx b/frontend/src/app/lobby/page.tsx index f1f5a41..74fd342 100644 --- a/frontend/src/app/lobby/page.tsx +++ b/frontend/src/app/lobby/page.tsx @@ -1,10 +1,12 @@ "use client"; import { useState, useEffect } from "react"; import { Markazi_Text, Nunito } from "next/font/google"; -import users from "muzak/data/users.json"; import Image from "next/image"; import Link from "next/link"; +import { useAuth } from "react-oidc-context"; import { useSession } from "muzak/contexts/SessionContext"; +import { getUser, api, Track } from "muzak/data/fetcher"; +import getSessionId from "muzak/data/session"; const nunito = Nunito({ weight: "900", @@ -17,34 +19,37 @@ export default function Lobby() { const [userList, setUserList] = useState([]); const [sessionSocket, setSessionSocket] = useState(null); const { sessionId, setSessionId } = useSession(); + const [sessionState, setSessionState] = useState("lobby"); + const [currentSong, setCurrentSong] = useState(); + const [songLoading, setSongLoading] = useState(false); + const auth = useAuth(); + const user = getUser(); + // Main message handler and dispatcher function onMessage(e) { - setTimeout(() => { - console.log(e); - let data = JSON.parse(e.data); - for (let key in data) { - if (key === "joined") { - addUser(data[key]); - } + console.log(e); + let data = JSON.parse(e.data); + for (let key in data) { + if (key === "joined") { + addUser(data[key]); + } else if (key == "track") { + getTrack(); } - console.log(data); - }, 1000); + } + console.log(data); } + // Setup session useEffect(() => { - console.log("session: ", sessionId); - let socket; - if (sessionId && !sessionSocket) { + //console.log("session: ", sessionId); + let socket: WebSocket | null; + if (sessionId) { socket = new WebSocket( `${process.env.NEXT_PUBLIC_WS_HOST}/voting/session/${sessionId}/`, ); socket.onmessage = onMessage; - if (!sessionSocket) { - setSessionSocket(socket); - console.log("session made: ", sessionId); - } else { - socket.close(); - } + setSessionSocket(socket); + // console.log("session made: ", sessionId); } return () => { if (socket) { @@ -52,6 +57,21 @@ export default function Lobby() { } }; }, [sessionId]); + async function setSession() { + const id = await getSessionId(); + setSessionId(id); + console.log("Session ID:", id); + } + useEffect(() => { + if (!auth.isLoading && !auth.error && auth.isAuthenticated) { + setSession(); + } + }, [auth]); + + function startSession() { + setSessionState("voting"); + getTrack(); + } function addUser(userName: string) { console.log(userList); @@ -59,7 +79,7 @@ export default function Lobby() { return; } const newUser = { - id: userList.length + 1, + id: userName, name: userName, avatar: `https://i.pravatar.cc/150?img=${userList.length + 1}`, host: userList.length === 0, @@ -68,79 +88,209 @@ export default function Lobby() { setUserList(newUserList); } - return ( -
-
-

- LOBBY -

-
+ async function getTrack() { + setSongLoading(true); + const { data: session } = await api.sessions.get({ session_id: sessionId }); + const trackId = session?.current_track; + if (trackId) { + const { data: track } = await api.tracks.detail({ id: trackId }); + console.log(session); + console.log(track); + setCurrentSong(track); + } else { + nextTrack(); + } + setSongLoading(false); + } -
-
-
- Players: {userList.length} -
- +
+

- START - + LOBBY +

- {userList.length === 0 ? ( -
- Waiting for players... +
+
+
+ Players: {userList.length} +
+
- ) : ( -
- {userList.map((user) => ( -
- {user.name} - - {user.name} - - {user.host && ( -
- Crown image -
- )} -
- ))} -
- )} -
-
- - -
-
- ); + {userList.length === 0 ? ( +
+ Waiting for players... +
+ ) : ( +
+ {userList.map((user) => ( +
+ {user.name} + + {user.name} + + {user.host && ( +
+ Crown image +
+ )} +
+ ))} +
+ )} + + + ); + } else if (sessionState == "voting") { + return ( +
+
+
+
+
+
+ +
+
+
+ +
+ + PAUSE + + +
+
+ +
+
+
+ {userList.map((user) => ( +
+ {user.name} +
+ {user.name} +
+
+ ))}{" "} +
+ {!songLoading && currentSong ? ( +
+ {currentSong.name} +
+ ) : ( +
+ Loading.. +
+ )} +
+ {userList.map((user) => ( +
+ {user.name} +
+ {user.name} +
+
+ ))}{" "} +
+
+ {!songLoading && currentSong && ( +
+

+ {currentSong.artist} +

+

+ {currentSong.name} +

+
+ )} +
+ +
+ {userList.map((user) => ( +
+ {user.name} +
+ {user.name} +
+
+ ))} +
+
+ ); + } } diff --git a/frontend/src/app/mobile-voting/page.tsx b/frontend/src/app/mobile-voting/page.tsx index cdfb9a5..cf4f05d 100644 --- a/frontend/src/app/mobile-voting/page.tsx +++ b/frontend/src/app/mobile-voting/page.tsx @@ -52,6 +52,20 @@ export default function MobileVoting() { ); } + // Main message handler and dispatcher + function onMessage(e) { + console.log(e); + let data = JSON.parse(e.data); + for (let key in data) { + if (key == "track") { + setYesHighlight(false); + setNoHighlight(false); + } + } + console.log(data); + //}, 1000); + } + // Initialize WebSocket connection when sessionId changes useEffect(() => { if (sessionId) { @@ -59,6 +73,7 @@ export default function MobileVoting() { const socket = new WebSocket( `${process.env.NEXT_PUBLIC_WS_HOST}/voting/session/${sessionId}/`, ); + socket.onmessage = onMessage; setSessionSocket(socket); //setTimeout(() => connect(), 2000); } diff --git a/frontend/src/data/fetcher.ts b/frontend/src/data/fetcher.ts index ac7b64a..74320d5 100644 --- a/frontend/src/data/fetcher.ts +++ b/frontend/src/data/fetcher.ts @@ -5,13 +5,15 @@ import { oidcConfig } from "muzak/config/auth"; import { User } from "oidc-client-ts"; export function getUser() { - let oidcStorage = window?.sessionStorage.getItem( - `oidc.user:${oidcConfig.authority}:${oidcConfig.client_id}`, - ); - if (!oidcStorage) { - return null; + if (typeof window !== "undefined") { + let oidcStorage = window?.sessionStorage.getItem( + `oidc.user:${oidcConfig.authority}:${oidcConfig.client_id}`, + ); + if (!oidcStorage) { + return null; + } + return User.fromStorageString(oidcStorage); } - return User.fromStorageString(oidcStorage); } const fetcher = Fetcher.for(); @@ -84,6 +86,7 @@ export const api = { me: fetcher.path("/api/profile/me").method("get").create(), }, sessions: { + get: fetcher.path("/voting/session/{session_id}/").method("get").create(), list: fetcher.path("/voting/session/").method("get").create(), }, };