betere sessies lol lmao even

This commit is contained in:
2025-08-16 09:51:29 +02:00
parent a1526ddc67
commit c7eb174a01
7 changed files with 331 additions and 110 deletions
@@ -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")},
),
]
+34 -15
View File
@@ -6,6 +6,7 @@ from django.contrib.auth.models import User
from muzak.auth import get_user_from_token from muzak.auth import get_user_from_token
from channels.db import database_sync_to_async from channels.db import database_sync_to_async
from playlist.models import Session, Track, Vote from playlist.models import Session, Track, Vote
import random
class SessionConsumer(WebsocketConsumer): class SessionConsumer(WebsocketConsumer):
@@ -27,6 +28,9 @@ class SessionConsumer(WebsocketConsumer):
# Receive message from WebSocket # Receive message from WebSocket
def receive(self, text_data): def receive(self, text_data):
user = self.scope["user"] # Waarschijnlijk altijd AnonymousUser 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) text_data_json = json.loads(text_data)
@@ -35,12 +39,9 @@ class SessionConsumer(WebsocketConsumer):
if token: if token:
user = get_user_from_token(token, log=True) user = get_user_from_token(token, log=True)
print(user) print(user)
session = Session.objects.get(session_id=sessionId)
action = text_data_json.get("action", None) action = text_data_json.get("action", None)
#:w
# sessionId = text_data_json.get("sessionId", None)
#session = Session.objects.get(id=sessionId)
if action == "message": if action == "message":
message = text_data_json["message"] message = text_data_json["message"]
# Send message to room group # Send message to room group
@@ -52,26 +53,44 @@ class SessionConsumer(WebsocketConsumer):
async_to_sync(self.channel_layer.group_send)( async_to_sync(self.channel_layer.group_send)(
self.session_name, {"type": "announce", "user": str(user)} self.session_name, {"type": "announce", "user": str(user)}
) )
# if action == "vote": if action == "vote":
# points = text_data_json.get("points", None) points = text_data_json.get("points", None)
# track = session.current_track; track = session.current_track;
# try: try:
# vote = Vote.objects.get(user=user, track=track, session=session) vote = Vote.objects.get(user=user, track=track, session=session)
# vote.points = points vote.points = points
# vote.save() vote.save()
# except Vote.DoesNotExist: except Vote.DoesNotExist:
# vote = Vote.objects.create(user=user, track=track, points=points, session=session) vote = Vote.objects.create(user=user, track=track, points=points, session=session)
# vote.save() vote.save()
if action == "pause": if action == "pause":
pass pass
if action == "next": 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): def announce(self, event):
user = event["user"] user = event["user"]
self.send(text_data=json.dumps({"joined": 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 # Receive message from room group
def chat_message(self, event): def chat_message(self, event):
+1
View File
@@ -6,4 +6,5 @@ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path("session/<str:session_id>/test", views.session, name="session"), path("session/<str:session_id>/test", views.session, name="session"),
path("session/", views.SessionListView.as_view(), name="api-session-list"), path("session/", views.SessionListView.as_view(), name="api-session-list"),
path("session/<str:session_id>/", views.SessionDetailView.as_view(), name="api-session-detail"),
] ]
+9
View File
@@ -22,3 +22,12 @@ class SessionListView(APIView):
else: else:
sessions = Session.objects.filter(voting_open=True) sessions = Session.objects.filter(voting_open=True)
return Response(SessionSerializer(sessions, many=True).data) 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)
+239 -89
View File
@@ -1,10 +1,12 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Markazi_Text, Nunito } from "next/font/google"; import { Markazi_Text, Nunito } from "next/font/google";
import users from "muzak/data/users.json";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useAuth } from "react-oidc-context";
import { useSession } from "muzak/contexts/SessionContext"; import { useSession } from "muzak/contexts/SessionContext";
import { getUser, api, Track } from "muzak/data/fetcher";
import getSessionId from "muzak/data/session";
const nunito = Nunito({ const nunito = Nunito({
weight: "900", weight: "900",
@@ -17,34 +19,37 @@ export default function Lobby() {
const [userList, setUserList] = useState<any[]>([]); const [userList, setUserList] = useState<any[]>([]);
const [sessionSocket, setSessionSocket] = useState<WebSocket | null>(null); const [sessionSocket, setSessionSocket] = useState<WebSocket | null>(null);
const { sessionId, setSessionId } = useSession(); const { sessionId, setSessionId } = useSession();
const [sessionState, setSessionState] = useState("lobby");
const [currentSong, setCurrentSong] = useState<Track>();
const [songLoading, setSongLoading] = useState(false);
const auth = useAuth();
const user = getUser();
// Main message handler and dispatcher
function onMessage(e) { function onMessage(e) {
setTimeout(() => { console.log(e);
console.log(e); let data = JSON.parse(e.data);
let data = JSON.parse(e.data); for (let key in data) {
for (let key in data) { if (key === "joined") {
if (key === "joined") { addUser(data[key]);
addUser(data[key]); } else if (key == "track") {
} getTrack();
} }
console.log(data); }
}, 1000); console.log(data);
} }
// Setup session
useEffect(() => { useEffect(() => {
console.log("session: ", sessionId); //console.log("session: ", sessionId);
let socket; let socket: WebSocket | null;
if (sessionId && !sessionSocket) { if (sessionId) {
socket = new WebSocket( socket = new WebSocket(
`${process.env.NEXT_PUBLIC_WS_HOST}/voting/session/${sessionId}/`, `${process.env.NEXT_PUBLIC_WS_HOST}/voting/session/${sessionId}/`,
); );
socket.onmessage = onMessage; socket.onmessage = onMessage;
if (!sessionSocket) { setSessionSocket(socket);
setSessionSocket(socket); // console.log("session made: ", sessionId);
console.log("session made: ", sessionId);
} else {
socket.close();
}
} }
return () => { return () => {
if (socket) { if (socket) {
@@ -52,6 +57,21 @@ export default function Lobby() {
} }
}; };
}, [sessionId]); }, [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) { function addUser(userName: string) {
console.log(userList); console.log(userList);
@@ -59,7 +79,7 @@ export default function Lobby() {
return; return;
} }
const newUser = { const newUser = {
id: userList.length + 1, id: userName,
name: userName, name: userName,
avatar: `https://i.pravatar.cc/150?img=${userList.length + 1}`, avatar: `https://i.pravatar.cc/150?img=${userList.length + 1}`,
host: userList.length === 0, host: userList.length === 0,
@@ -68,79 +88,209 @@ export default function Lobby() {
setUserList(newUserList); setUserList(newUserList);
} }
return ( async function getTrack() {
<main className="flex min-h-screen flex-col items-center justify-between p-24"> setSongLoading(true);
<div> const { data: session } = await api.sessions.get({ session_id: sessionId });
<h1 const trackId = session?.current_track;
className={`${nunito.className} text-outline text-[42px] tracking-tighter text-yellow-500`} if (trackId) {
> const { data: track } = await api.tracks.detail({ id: trackId });
LOBBY console.log(session);
</h1> console.log(track);
</div> setCurrentSong(track);
} else {
nextTrack();
}
setSongLoading(false);
}
<div className="flex min-h-[532px] min-w-[500px] flex-col items-center justify-start gap-4 rounded-md bg-blue-950/80 p-8"> async function nextTrack() {
<div className="flex w-full flex-row items-center justify-between px-1"> setSongLoading(true);
<div className="text-3xl font-bold text-yellow-500"> sessionSocket.send(
Players: {userList.length} JSON.stringify({
</div> action: "next",
<Link token: user.access_token,
href="/voting" }),
className="rounded-lg border-2 border-black bg-yellow-500 p-4 text-2xl font-extrabold text-blue-600 hover:bg-yellow-400" );
}
if (sessionState == "lobby") {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div>
<h1
className={`${nunito.className} text-outline text-[42px] tracking-tighter text-yellow-500`}
> >
START LOBBY
</Link> </h1>
</div> </div>
{userList.length === 0 ? ( <div className="flex min-h-[532px] min-w-[500px] flex-col items-center justify-start gap-4 rounded-md bg-blue-950/80 p-8">
<div className="mt-31 min-w-52 text-3xl font-semibold text-white drop-shadow-sm drop-shadow-gray-900"> <div className="flex w-full flex-row items-center justify-between px-1">
Waiting for players... <div className="text-3xl font-bold text-yellow-500">
Players: {userList.length}
</div>
<button
onClick={startSession}
className="rounded-lg border-2 border-black bg-yellow-500 p-4 text-2xl font-extrabold text-blue-600 hover:bg-yellow-400"
>
START!
</button>
</div> </div>
) : (
<div className="grid min-w-52 grid-flow-row grid-cols-2 items-start justify-start gap-4">
{userList.map((user) => (
<div
key={user.id}
className="flex min-w-32 items-center gap-x-4 rounded-l-[40px] rounded-r-lg border-2 bg-orange-400/90 p-2"
>
<img
src={user.avatar}
alt={user.name}
className="h-16 w-16 rounded-full bg-gray-300"
/>
<span className="text-2xl font-semibold text-white">
{user.name}
</span>
{user.host && (
<div className="rounded-full bg-blue-950 p-[5px]">
<Image
src="/crown.png"
width={24}
height={24}
alt="Crown image"
className="p-[1px]"
/>
</div>
)}
</div>
))}
</div>
)}
</div>
<div className="mt-4 flex items-center justify-between gap-4"> {userList.length === 0 ? (
<button <div className="mt-31 min-w-52 text-3xl font-semibold text-white drop-shadow-sm drop-shadow-gray-900">
className="rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600" Waiting for players...
onClick={() => deleteUserFromParty()} </div>
> ) : (
del <div className="grid min-w-52 grid-flow-row grid-cols-2 items-start justify-start gap-4">
</button> {userList.map((user) => (
<button <div
className="rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600" key={user.id}
onClick={() => addUserToParty()} className="flex min-w-32 items-center gap-x-4 rounded-l-[40px] rounded-r-lg border-2 bg-orange-400/90 p-2"
> >
add <img
</button> src={user.avatar}
</div> alt={user.name}
</main> className="h-16 w-16 rounded-full bg-gray-300"
); />
<span className="text-2xl font-semibold text-white">
{user.name}
</span>
{user.host && (
<div className="rounded-full bg-blue-950 p-[5px]">
<Image
src="/crown.png"
width={24}
height={24}
alt="Crown image"
className="p-[1px]"
/>
</div>
)}
</div>
))}
</div>
)}
</div>
</main>
);
} else if (sessionState == "voting") {
return (
<main className="flex min-h-screen flex-col items-center justify-between gap-4 p-24">
<div className="flex w-full items-start justify-between text-center">
<div className="flex-1"></div>
<div className="flex flex-1 items-center justify-center">
<div className="flex h-[170px] w-[170px] items-center justify-center rounded-full border-2 bg-orange-400">
<div className="text-5xl text-white drop-shadow-sm drop-shadow-black">
<button onClick={getTrack}>30</button>
</div>
</div>
</div>
<div className="flex flex-1 flex-row items-center justify-end gap-4">
<Link
href="/pause"
className="w-30 rounded-lg border-2 border-black bg-yellow-500 p-4 text-2xl font-extrabold text-blue-600 hover:bg-yellow-400"
>
PAUSE
</Link>
<button
className="w-30 rounded-lg border-2 border-black bg-yellow-500 p-4 text-2xl font-extrabold text-blue-600 hover:bg-yellow-400"
onClick={nextTrack}
>
NEXT
</button>
</div>
</div>
<div className="flex flex-col items-center justify-center">
<div className="flex flex-row items-center justify-center gap-x-32">
<div className="grid min-w-52 grid-flow-row grid-cols-3 items-start justify-start gap-4 rounded bg-blue-950/80 p-4">
{userList.map((user) => (
<div
key={user.id}
className="flex h-[120px] w-[120px] flex-col items-center justify-center p-4"
>
<img
src={user.avatar}
alt={user.name}
width={75}
height={75}
/>
<div className="mt-1 min-w-[90px] rounded-3xl px-3 text-center text-3xl font-semibold text-white">
{user.name}
</div>
</div>
))}{" "}
</div>
{!songLoading && currentSong ? (
<div className="rounded-md bg-black p-2">
<Image
src={currentSong.album_cover}
alt={currentSong.name}
width={500}
height={500}
/>
</div>
) : (
<div className="rounded-md bg-black p-2">
<Image
src="/meowl.png"
alt="Loading.."
width={500}
height={500}
/>
</div>
)}
<div className="grid min-w-52 grid-flow-row grid-cols-3 items-start justify-start gap-4 rounded bg-blue-950/80 p-4">
{userList.map((user) => (
<div
key={user.id}
className="flex h-[120px] w-[120px] flex-col items-center justify-center p-4"
>
<img
src={user.avatar}
alt={user.name}
width={75}
height={75}
/>
<div className="mt-1 min-w-[90px] rounded-3xl px-3 text-center text-3xl font-semibold text-white">
{user.name}
</div>
</div>
))}{" "}
</div>
</div>
{!songLoading && currentSong && (
<div className="flex flex-col items-center justify-start">
<h2
className={`${nunito.className} text-outline text-[42px] tracking-tighter text-yellow-500`}
>
{currentSong.artist}
</h2>
<h2
className={`${nunito.className} text-outline text-[42px] tracking-tighter text-yellow-500`}
>
{currentSong.name}
</h2>
</div>
)}
</div>
<div className="flex flex-row items-start justify-start gap-10 rounded bg-blue-950/80 p-4">
{userList.map((user) => (
<div
key={user.id}
className="flex h-[100px] w-[100px] flex-col items-center justify-center p-4"
>
<img src={user.avatar} alt={user.name} width={75} height={75} />
<div className="mt-1 min-w-[90px] rounded-3xl px-3 text-center text-3xl font-semibold text-white">
{user.name}
</div>
</div>
))}
</div>
</main>
);
}
} }
+15
View File
@@ -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 // Initialize WebSocket connection when sessionId changes
useEffect(() => { useEffect(() => {
if (sessionId) { if (sessionId) {
@@ -59,6 +73,7 @@ export default function MobileVoting() {
const socket = new WebSocket( const socket = new WebSocket(
`${process.env.NEXT_PUBLIC_WS_HOST}/voting/session/${sessionId}/`, `${process.env.NEXT_PUBLIC_WS_HOST}/voting/session/${sessionId}/`,
); );
socket.onmessage = onMessage;
setSessionSocket(socket); setSessionSocket(socket);
//setTimeout(() => connect(), 2000); //setTimeout(() => connect(), 2000);
} }
+9 -6
View File
@@ -5,13 +5,15 @@ import { oidcConfig } from "muzak/config/auth";
import { User } from "oidc-client-ts"; import { User } from "oidc-client-ts";
export function getUser() { export function getUser() {
let oidcStorage = window?.sessionStorage.getItem( if (typeof window !== "undefined") {
`oidc.user:${oidcConfig.authority}:${oidcConfig.client_id}`, let oidcStorage = window?.sessionStorage.getItem(
); `oidc.user:${oidcConfig.authority}:${oidcConfig.client_id}`,
if (!oidcStorage) { );
return null; if (!oidcStorage) {
return null;
}
return User.fromStorageString(oidcStorage);
} }
return User.fromStorageString(oidcStorage);
} }
const fetcher = Fetcher.for<paths>(); const fetcher = Fetcher.for<paths>();
@@ -84,6 +86,7 @@ export const api = {
me: fetcher.path("/api/profile/me").method("get").create(), me: fetcher.path("/api/profile/me").method("get").create(),
}, },
sessions: { sessions: {
get: fetcher.path("/voting/session/{session_id}/").method("get").create(),
list: fetcher.path("/voting/session/").method("get").create(), list: fetcher.path("/voting/session/").method("get").create(),
}, },
}; };