Compare commits
No commits in common. "6362b9750855fc03e8c2093c6433c5df6963901e" and "65beeff4f01235d4e61bc149a81ec577b33aec5d" have entirely different histories.
6362b97508
...
65beeff4f0
16 changed files with 48 additions and 132 deletions
|
|
@ -1,21 +0,0 @@
|
||||||
// Création du client postgres-js. Deux modes :
|
|
||||||
// 1. DATABASE_URL (DSN complet) — pratique en dev.
|
|
||||||
// 2. Variables PG* (PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE) — recommandé en
|
|
||||||
// prod : le mot de passe est passé tel quel (pas d'encodage d'URL à gérer).
|
|
||||||
// postgres-js lit automatiquement les PG* de l'environnement quand on ne lui
|
|
||||||
// passe pas de DSN.
|
|
||||||
|
|
||||||
import postgres from "postgres"
|
|
||||||
import { env } from "../env"
|
|
||||||
|
|
||||||
export const hasDbConfig = !!env.databaseUrl || !!process.env.PGHOST
|
|
||||||
|
|
||||||
export function createSqlClient(opts: { max?: number } = {}) {
|
|
||||||
if (env.databaseUrl) {
|
|
||||||
return postgres(env.databaseUrl, opts)
|
|
||||||
}
|
|
||||||
if (process.env.PGHOST) {
|
|
||||||
return postgres(opts)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
@ -2,16 +2,17 @@
|
||||||
// le jeu retombe sur la banque de questions en dur (dev sans infra).
|
// le jeu retombe sur la banque de questions en dur (dev sans infra).
|
||||||
|
|
||||||
import { drizzle } from "drizzle-orm/postgres-js"
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
import { createSqlClient } from "./client"
|
import postgres from "postgres"
|
||||||
|
import { env } from "../env"
|
||||||
import * as schema from "./schema"
|
import * as schema from "./schema"
|
||||||
|
|
||||||
export type Database = ReturnType<typeof drizzle<typeof schema>>
|
export type Database = ReturnType<typeof drizzle<typeof schema>>
|
||||||
|
|
||||||
function createDb(): Database | null {
|
function createDb(): Database | null {
|
||||||
const client = createSqlClient()
|
if (!env.databaseUrl) {
|
||||||
if (!client) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
const client = postgres(env.databaseUrl)
|
||||||
return drizzle(client, { schema })
|
return drizzle(client, { schema })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@
|
||||||
// bun run db:migrate
|
// bun run db:migrate
|
||||||
import { drizzle } from "drizzle-orm/postgres-js"
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
import { migrate } from "drizzle-orm/postgres-js/migrator"
|
import { migrate } from "drizzle-orm/postgres-js/migrator"
|
||||||
import { createSqlClient } from "./client"
|
import postgres from "postgres"
|
||||||
|
import { env } from "../env"
|
||||||
|
|
||||||
const client = createSqlClient({ max: 1 })
|
if (!env.databaseUrl) {
|
||||||
if (!client) {
|
console.error("DATABASE_URL manquant — rien à migrer.")
|
||||||
console.error("Aucune config DB (DATABASE_URL ou PG*) — rien à migrer.")
|
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const client = postgres(env.databaseUrl, { max: 1 })
|
||||||
const db = drizzle(client)
|
const db = drizzle(client)
|
||||||
|
|
||||||
await migrate(db, { migrationsFolder: "./drizzle" })
|
await migrate(db, { migrationsFolder: "./drizzle" })
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,21 @@
|
||||||
|
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { drizzle } from "drizzle-orm/postgres-js"
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
import { createSqlClient } from "./client"
|
import postgres from "postgres"
|
||||||
|
import { env } from "../env"
|
||||||
import { quizCategory, quizQuestion, type NewQuizQuestion } from "./schema"
|
import { quizCategory, quizQuestion, type NewQuizQuestion } from "./schema"
|
||||||
|
|
||||||
|
if (!env.databaseUrl) {
|
||||||
|
console.error("DATABASE_URL manquant — impossible de seeder.")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
const COUNT = Number(process.env.IMAGE_SEED_COUNT ?? 151)
|
const COUNT = Number(process.env.IMAGE_SEED_COUNT ?? 151)
|
||||||
const CATEGORY = "Pokémon"
|
const CATEGORY = "Pokémon"
|
||||||
const PROMPT = "Quel est ce Pokémon ?"
|
const PROMPT = "Quel est ce Pokémon ?"
|
||||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||||
|
|
||||||
const client = createSqlClient({ max: 1 })
|
const client = postgres(env.databaseUrl, { max: 1 })
|
||||||
if (!client) {
|
|
||||||
console.error("Aucune config DB (DATABASE_URL ou PG*) — impossible de seeder.")
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
const db = drizzle(client, { schema: { quizCategory, quizQuestion } })
|
const db = drizzle(client, { schema: { quizCategory, quizQuestion } })
|
||||||
|
|
||||||
async function upsertCategory(name: string): Promise<string> {
|
async function upsertCategory(name: string): Promise<string> {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { drizzle } from "drizzle-orm/postgres-js"
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
import { createSqlClient } from "./client"
|
import postgres from "postgres"
|
||||||
|
import { env } from "../env"
|
||||||
import {
|
import {
|
||||||
quizCategory,
|
quizCategory,
|
||||||
quizQuestion,
|
quizQuestion,
|
||||||
|
|
@ -13,11 +14,12 @@ import {
|
||||||
} from "./schema"
|
} from "./schema"
|
||||||
import { QUIZ_QUESTIONS } from "../game/modes/quiz/questions"
|
import { QUIZ_QUESTIONS } from "../game/modes/quiz/questions"
|
||||||
|
|
||||||
const client = createSqlClient({ max: 1 })
|
if (!env.databaseUrl) {
|
||||||
if (!client) {
|
console.error("DATABASE_URL manquant — impossible de seeder.")
|
||||||
console.error("Aucune config DB (DATABASE_URL ou PG*) — impossible de seeder.")
|
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const client = postgres(env.databaseUrl, { max: 1 })
|
||||||
const db = drizzle(client, { schema: { quizCategory, quizQuestion } })
|
const db = drizzle(client, { schema: { quizCategory, quizQuestion } })
|
||||||
|
|
||||||
// Catégories Open Trivia DB pertinentes (id OpenTDB → nom affiché).
|
// Catégories Open Trivia DB pertinentes (id OpenTDB → nom affiché).
|
||||||
|
|
|
||||||
|
|
@ -52,15 +52,6 @@ export function registerRoomHandlers(
|
||||||
io.to(room.code).emit("room:state", rooms.toSnapshot(room))
|
io.to(room.code).emit("room:state", rooms.toSnapshot(room))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renvoie au joueur SES propres titres (source de vérité, anti-désync client).
|
|
||||||
const emitMyTracks = (room: ServerRoom, playerId: string) => {
|
|
||||||
socket.emit("blindtest:myTracks", {
|
|
||||||
tracks: room.blindtestTracks
|
|
||||||
.filter((t) => t.submittedBy === playerId)
|
|
||||||
.map((t) => ({ trackId: t.id, title: t.title, youtubeId: t.youtubeId })),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on("room:create", (payload, ack) => {
|
socket.on("room:create", (payload, ack) => {
|
||||||
// Le pseudo est choisi dans la room (player:setName) : on crée sans nom.
|
// Le pseudo est choisi dans la room (player:setName) : on crée sans nom.
|
||||||
const name = cleanName(payload?.playerName) ?? ""
|
const name = cleanName(payload?.playerName) ?? ""
|
||||||
|
|
@ -120,7 +111,6 @@ export function registerRoomHandlers(
|
||||||
})
|
})
|
||||||
io.to(room.code).emit("player:rejoined", { playerId: player.id })
|
io.to(room.code).emit("player:rejoined", { playerId: player.id })
|
||||||
broadcastState(room)
|
broadcastState(room)
|
||||||
emitMyTracks(room, player.id)
|
|
||||||
// Re-pousse la manche en cours au socket reconnecté (si partie en cours).
|
// Re-pousse la manche en cours au socket reconnecté (si partie en cours).
|
||||||
room.game?.resync(socket.id, player.id)
|
room.game?.resync(socket.id, player.id)
|
||||||
})
|
})
|
||||||
|
|
@ -303,7 +293,6 @@ export function registerRoomHandlers(
|
||||||
title: meta.title,
|
title: meta.title,
|
||||||
count: mine.length + 1,
|
count: mine.length + 1,
|
||||||
})
|
})
|
||||||
emitMyTracks(room, playerId)
|
|
||||||
broadcastState(room)
|
broadcastState(room)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -322,7 +311,6 @@ export function registerRoomHandlers(
|
||||||
const removed = room.blindtestTracks.length < before
|
const removed = room.blindtestTracks.length < before
|
||||||
ack({ ok: removed })
|
ack({ ok: removed })
|
||||||
if (removed) {
|
if (removed) {
|
||||||
emitMyTracks(room, playerId)
|
|
||||||
broadcastState(room)
|
broadcastState(room)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { Disc3, Pause, Play, Rewind, Volume2 } from "lucide-react"
|
import { Disc3, Pause, Play, Rewind } from "lucide-react"
|
||||||
import type {
|
import type {
|
||||||
BlindtestAnswer,
|
BlindtestAnswer,
|
||||||
BlindtestMode,
|
BlindtestMode,
|
||||||
|
|
@ -42,10 +42,6 @@ export function BlindtestView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
const truth = reveal ? (reveal.truth as BlindtestRevealTruth) : null
|
const truth = reveal ? (reveal.truth as BlindtestRevealTruth) : null
|
||||||
const showReveal = truth !== null
|
const showReveal = truth !== null
|
||||||
|
|
||||||
// Les navigateurs bloquent la lecture audible sans geste utilisateur : le non-DJ
|
|
||||||
// doit "armer" le son une fois (le DJ a déjà cliqué Play, donc dispensé).
|
|
||||||
const [audioArmed, setAudioArmed] = useState(false)
|
|
||||||
|
|
||||||
// Non-DJ : on suit le DJ via media:sync (avec compensation de latence).
|
// Non-DJ : on suit le DJ via media:sync (avec compensation de latence).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDj || !ready || !mediaSync) {
|
if (isDj || !ready || !mediaSync) {
|
||||||
|
|
@ -54,25 +50,14 @@ export function BlindtestView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
const elapsed = (Date.now() - mediaSync.atServerTs) / 1000
|
const elapsed = (Date.now() - mediaSync.atServerTs) / 1000
|
||||||
if (mediaSync.action === "play") {
|
if (mediaSync.action === "play") {
|
||||||
api.seek(mediaSync.positionSec + Math.max(0, elapsed))
|
api.seek(mediaSync.positionSec + Math.max(0, elapsed))
|
||||||
if (audioArmed) {
|
api.play()
|
||||||
api.play()
|
|
||||||
}
|
|
||||||
} else if (mediaSync.action === "pause") {
|
} else if (mediaSync.action === "pause") {
|
||||||
api.seek(mediaSync.positionSec)
|
api.seek(mediaSync.positionSec)
|
||||||
api.pause()
|
api.pause()
|
||||||
} else {
|
} else {
|
||||||
api.seek(mediaSync.positionSec)
|
api.seek(mediaSync.positionSec)
|
||||||
}
|
}
|
||||||
}, [mediaSync, isDj, ready, api, audioArmed])
|
}, [mediaSync, isDj, ready, api])
|
||||||
|
|
||||||
function armAudio() {
|
|
||||||
setAudioArmed(true)
|
|
||||||
if (mediaSync && mediaSync.action !== "pause") {
|
|
||||||
const elapsed = (Date.now() - mediaSync.atServerTs) / 1000
|
|
||||||
api.seek(mediaSync.positionSec + Math.max(0, elapsed))
|
|
||||||
}
|
|
||||||
api.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Au reveal, on coupe le son.
|
// Au reveal, on coupe le son.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -113,16 +98,6 @@ export function BlindtestView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Non-DJ : geste requis pour débloquer l'audio (autoplay policy). */}
|
|
||||||
{!showReveal && !isDj && !audioArmed && (
|
|
||||||
<button
|
|
||||||
onClick={armAudio}
|
|
||||||
className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 bg-black/60 text-white backdrop-blur-sm"
|
|
||||||
>
|
|
||||||
<Volume2 className="size-10" />
|
|
||||||
<span className="font-heading font-bold">{t.blindtest.enableSound}</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isDj && !showReveal && (
|
{isDj && !showReveal && (
|
||||||
|
|
|
||||||
|
|
@ -550,6 +550,12 @@ export function LobbyView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MyTrack {
|
||||||
|
trackId: string
|
||||||
|
title: string
|
||||||
|
youtubeId: string
|
||||||
|
}
|
||||||
|
|
||||||
function TrackSubmission({
|
function TrackSubmission({
|
||||||
tracksPerPlayer,
|
tracksPerPlayer,
|
||||||
myCount,
|
myCount,
|
||||||
|
|
@ -559,12 +565,11 @@ function TrackSubmission({
|
||||||
}) {
|
}) {
|
||||||
const submitTrack = useRoomStore((s) => s.submitTrack)
|
const submitTrack = useRoomStore((s) => s.submitTrack)
|
||||||
const removeTrack = useRoomStore((s) => s.removeTrack)
|
const removeTrack = useRoomStore((s) => s.removeTrack)
|
||||||
// Source de vérité : le serveur renvoie nos titres (anti-désync au remount).
|
|
||||||
const myTracks = useRoomStore((s) => s.myTracks)
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const [url, setUrl] = useState("")
|
const [url, setUrl] = useState("")
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
const [feedback, setFeedback] = useState<string | null>(null)
|
const [feedback, setFeedback] = useState<string | null>(null)
|
||||||
|
const [tracks, setTracks] = useState<MyTrack[]>([])
|
||||||
const quotaReached = myCount >= tracksPerPlayer
|
const quotaReached = myCount >= tracksPerPlayer
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
|
|
@ -574,8 +579,11 @@ function TrackSubmission({
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
setFeedback(null)
|
setFeedback(null)
|
||||||
const res = await submitTrack(url.trim())
|
const res = await submitTrack(url.trim())
|
||||||
if (res.accepted) {
|
if (res.accepted && res.trackId && res.youtubeId) {
|
||||||
// La liste se met à jour via l'event serveur blindtest:myTracks.
|
setTracks((t) => [
|
||||||
|
...t,
|
||||||
|
{ trackId: res.trackId!, title: res.title ?? "", youtubeId: res.youtubeId! },
|
||||||
|
])
|
||||||
setUrl("")
|
setUrl("")
|
||||||
} else {
|
} else {
|
||||||
setFeedback(res.reason ?? t.lobby.rejected)
|
setFeedback(res.reason ?? t.lobby.rejected)
|
||||||
|
|
@ -584,7 +592,10 @@ function TrackSubmission({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(trackId: string) {
|
async function remove(trackId: string) {
|
||||||
await removeTrack(trackId)
|
const ok = await removeTrack(trackId)
|
||||||
|
if (ok) {
|
||||||
|
setTracks((t) => t.filter((x) => x.trackId !== trackId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -593,9 +604,9 @@ function TrackSubmission({
|
||||||
{t.lobby.myTracks(myCount, tracksPerPlayer)}
|
{t.lobby.myTracks(myCount, tracksPerPlayer)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{myTracks.length > 0 && (
|
{tracks.length > 0 && (
|
||||||
<ul className="flex flex-col gap-2">
|
<ul className="flex flex-col gap-2">
|
||||||
{myTracks.map((track) => (
|
{tracks.map((track) => (
|
||||||
<li
|
<li
|
||||||
key={track.trackId}
|
key={track.trackId}
|
||||||
className="bg-muted/40 flex items-center gap-3 rounded-lg p-2"
|
className="bg-muted/40 flex items-center gap-3 rounded-lg p-2"
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,7 @@ export function QuizView({ snapshot }: { snapshot: RoomSnapshot }) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isText ? (
|
{isText ? (
|
||||||
<FreeAnswer
|
<FreeAnswer disabled={showReveal || hasVoted} onSubmit={voteText} t={t} />
|
||||||
key={round.endsAt}
|
|
||||||
disabled={showReveal || hasVoted}
|
|
||||||
onSubmit={voteText}
|
|
||||||
t={t}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{(question.choices ?? []).map((choice, index) => (
|
{(question.choices ?? []).map((choice, index) => (
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,6 @@ export const en: Dict = {
|
||||||
validate: "Submit",
|
validate: "Submit",
|
||||||
},
|
},
|
||||||
blindtest: {
|
blindtest: {
|
||||||
enableSound: "Enable sound",
|
|
||||||
djHint: "You're the DJ 🎧 — control playback (and vote like everyone)",
|
djHint: "You're the DJ 🎧 — control playback (and vote like everyone)",
|
||||||
play: "Play",
|
play: "Play",
|
||||||
pause: "Pause",
|
pause: "Pause",
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,6 @@ export const fr = {
|
||||||
validate: "Valider",
|
validate: "Valider",
|
||||||
},
|
},
|
||||||
blindtest: {
|
blindtest: {
|
||||||
enableSound: "Activer le son",
|
|
||||||
djHint: "Tu es le DJ 🎧 — pilote la lecture (et vote comme les autres)",
|
djHint: "Tu es le DJ 🎧 — pilote la lecture (et vote comme les autres)",
|
||||||
play: "Play",
|
play: "Play",
|
||||||
pause: "Pause",
|
pause: "Pause",
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,6 @@ export function useYoutube(youtubeId: string) {
|
||||||
modestbranding: 1,
|
modestbranding: 1,
|
||||||
rel: 0,
|
rel: 0,
|
||||||
playsinline: 1,
|
playsinline: 1,
|
||||||
// Évite l'avertissement postMessage "target origin does not match"
|
|
||||||
// et fiabilise la transmission des commandes au lecteur.
|
|
||||||
enablejsapi: 1,
|
|
||||||
origin: window.location.origin,
|
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
onReady: () => {
|
onReady: () => {
|
||||||
|
|
|
||||||
|
|
@ -129,15 +129,7 @@ export function RoomPage({ code }: { code: string }) {
|
||||||
<PlayerCards
|
<PlayerCards
|
||||||
snapshot={snapshot}
|
snapshot={snapshot}
|
||||||
playerId={playerId}
|
playerId={playerId}
|
||||||
// En blindtest "qui l'a ajouté" (et mixte), on cache l'état de
|
showVoteStatus={snapshot.status === "in_round"}
|
||||||
// vote : le contributeur ne vote jamais → sinon il est repérable.
|
|
||||||
showVoteStatus={
|
|
||||||
snapshot.status === "in_round" &&
|
|
||||||
!(
|
|
||||||
round?.type === "blindtest" &&
|
|
||||||
snapshot.settings.blindtestMode !== "title_artist"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
votedIds={voteProgress?.voted}
|
votedIds={voteProgress?.voted}
|
||||||
/>
|
/>
|
||||||
{round?.type === "blindtest" ? (
|
{round?.type === "blindtest" ? (
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import {
|
||||||
type ErrorPayload,
|
type ErrorPayload,
|
||||||
type MediaControlPayload,
|
type MediaControlPayload,
|
||||||
type MediaSyncPayload,
|
type MediaSyncPayload,
|
||||||
type MyTrackInfo,
|
|
||||||
type PlayerScore,
|
type PlayerScore,
|
||||||
type RoomCreatedPayload,
|
type RoomCreatedPayload,
|
||||||
type RoomSnapshot,
|
type RoomSnapshot,
|
||||||
|
|
@ -52,8 +51,6 @@ interface RoomState {
|
||||||
pseudoSet: boolean
|
pseudoSet: boolean
|
||||||
snapshot: RoomSnapshot | null
|
snapshot: RoomSnapshot | null
|
||||||
lastError: ErrorPayload | null
|
lastError: ErrorPayload | null
|
||||||
/** Mes titres blindtest (renvoyés par le serveur, source de vérité). */
|
|
||||||
myTracks: MyTrackInfo[]
|
|
||||||
|
|
||||||
// État de jeu (poussé par le moteur serveur).
|
// État de jeu (poussé par le moteur serveur).
|
||||||
round: ActiveRound | null
|
round: ActiveRound | null
|
||||||
|
|
@ -99,7 +96,6 @@ export const useRoomStore = create<RoomState>((set, get) => ({
|
||||||
pseudoSet: !!saved?.name,
|
pseudoSet: !!saved?.name,
|
||||||
snapshot: null,
|
snapshot: null,
|
||||||
lastError: null,
|
lastError: null,
|
||||||
myTracks: [],
|
|
||||||
round: null,
|
round: null,
|
||||||
voteProgress: null,
|
voteProgress: null,
|
||||||
reveal: null,
|
reveal: null,
|
||||||
|
|
@ -246,7 +242,6 @@ export const useRoomStore = create<RoomState>((set, get) => ({
|
||||||
pseudoSet: false,
|
pseudoSet: false,
|
||||||
snapshot: null,
|
snapshot: null,
|
||||||
lastError: null,
|
lastError: null,
|
||||||
myTracks: [],
|
|
||||||
round: null,
|
round: null,
|
||||||
voteProgress: null,
|
voteProgress: null,
|
||||||
reveal: null,
|
reveal: null,
|
||||||
|
|
@ -308,9 +303,6 @@ socket.on("room:state", (snapshot) =>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
socket.on("error", (error) => useRoomStore.setState({ lastError: error }))
|
socket.on("error", (error) => useRoomStore.setState({ lastError: error }))
|
||||||
socket.on("blindtest:myTracks", ({ tracks }) =>
|
|
||||||
useRoomStore.setState({ myTracks: tracks })
|
|
||||||
)
|
|
||||||
|
|
||||||
socket.on("round:start", (payload) =>
|
socket.on("round:start", (payload) =>
|
||||||
useRoomStore.setState((s) => {
|
useRoomStore.setState((s) => {
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,7 @@ services:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
HOST: 0.0.0.0
|
HOST: 0.0.0.0
|
||||||
PORT: 3001
|
PORT: 3001
|
||||||
# Connexion via PG* (mot de passe passé tel quel, pas d'encodage d'URL).
|
DATABASE_URL: postgres://${POSTGRES_USER:-nerdware}:${POSTGRES_PASSWORD:-nerdware}@postgres:5432/${POSTGRES_DB:-nerdware}
|
||||||
PGHOST: postgres
|
|
||||||
PGPORT: 5432
|
|
||||||
PGUSER: ${POSTGRES_USER:-nerdware}
|
|
||||||
PGPASSWORD: ${POSTGRES_PASSWORD:-nerdware}
|
|
||||||
PGDATABASE: ${POSTGRES_DB:-nerdware}
|
|
||||||
ADMIN_TOKEN: ${ADMIN_TOKEN}
|
ADMIN_TOKEN: ${ADMIN_TOKEN}
|
||||||
CORS_ORIGINS: ${CORS_ORIGINS}
|
CORS_ORIGINS: ${CORS_ORIGINS}
|
||||||
UPLOADS_DIR: /data/uploads
|
UPLOADS_DIR: /data/uploads
|
||||||
|
|
|
||||||
|
|
@ -78,17 +78,6 @@ export interface RemoveTrackPayload {
|
||||||
trackId: string
|
trackId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Un titre soumis par le joueur lui-même (renvoyé en privé, source de vérité). */
|
|
||||||
export interface MyTrackInfo {
|
|
||||||
trackId: string
|
|
||||||
title: string
|
|
||||||
youtubeId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MyTracksPayload {
|
|
||||||
tracks: MyTrackInfo[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RoundStartPayload {
|
export interface RoundStartPayload {
|
||||||
type: RoundType
|
type: RoundType
|
||||||
djId?: string
|
djId?: string
|
||||||
|
|
@ -186,7 +175,6 @@ export interface ServerToClientEvents {
|
||||||
"player:left": (payload: PlayerRefPayload) => void
|
"player:left": (payload: PlayerRefPayload) => void
|
||||||
"player:rejoined": (payload: PlayerRefPayload) => void
|
"player:rejoined": (payload: PlayerRefPayload) => void
|
||||||
"blindtest:submitOk": (payload: SubmitOkPayload) => void
|
"blindtest:submitOk": (payload: SubmitOkPayload) => void
|
||||||
"blindtest:myTracks": (payload: MyTracksPayload) => void
|
|
||||||
"round:start": (payload: RoundStartPayload) => void
|
"round:start": (payload: RoundStartPayload) => void
|
||||||
"media:sync": (payload: MediaSyncPayload) => void
|
"media:sync": (payload: MediaSyncPayload) => void
|
||||||
"round:voteAck": (payload: VoteAckPayload) => void
|
"round:voteAck": (payload: VoteAckPayload) => void
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue