diff --git a/.env.example b/.env.example index 37b9549c..88297302 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,4 @@ WEB_ORIGIN=http://localhost:3000 # Default: http://localhost:3000, for allow all origins use '*' SOCKET_URL=http://localhost:3001 # Default: http://localhost:3001 +MANAGER_PASSWORD=PASSWORD +MUSIC_ENABLED=false \ No newline at end of file diff --git a/.gitignore b/.gitignore index ed1665d7..77c81f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /node_modules .env .secrets -release.json \ No newline at end of file +release.json +quizz/* +!quizz/example.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 63d40be9..557d294c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,7 @@ "javascriptreact", "typescript", "typescriptreact" - ] + ], + + "prettier.semi": false } diff --git a/Dockerfile b/Dockerfile index 02bec794..c40e311c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,14 +60,14 @@ COPY --from=builder /app/packages/web/public ./packages/web/public COPY --from=builder /app/packages/socket/dist ./packages/socket/dist # Copy the game default config -COPY --from=builder /app/config ./config +COPY --from=builder /app/quizz ./quizz # Expose the web and socket ports EXPOSE 3000 5505 # Environment variables ENV NODE_ENV=production -ENV CONFIG_PATH=/app/config + # Start both services (Next.js web app + Socket server) CMD ["sh", "-c", "node packages/web/server.js & node packages/socket/dist/index.cjs"] diff --git a/README.md b/README.md index 2cd82a95..8010fca7 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,16 @@ Using Docker Compose (recommended): You can find the docker compose configuration in the repository: [docker-compose.yml](/compose.yml) +copy the .env.example to .env. + +```bash + cp .env.example .env +``` + +start the container + ```bash -docker compose up -d + docker compose up -d ``` Or using Docker directly: @@ -55,14 +63,15 @@ docker run -d \ -v ./config:/app/config \ -e WEB_ORIGIN=http://localhost:3000 \ -e SOCKET_URL=http://localhost:3001 \ + -e MANAGER_PASSWORD=PASSWORD \ + -e MUSIC_ENABLED=false \ ralex91/rahoot:latest ``` **Configuration Volume:** -The `-v ./config:/app/config` option mounts a local `config` folder to persist your game settings and quizzes. This allows you to: +The `-v ./quizz:/app/quizz` option mounts a local `quizz` folder to persist your game settings and quizzes. This allows you to: - Edit your configuration files directly on your host machine -- Keep your settings when updating the container - Easily backup your quizzes and game configuration The folder will be created automatically on first run with an example quiz to get you started. @@ -104,27 +113,31 @@ pnpm start The configuration is split into two main parts: -### 1. Game Configuration (`config/game.json`) +### 1. Game Configuration (`.env`) Main game settings: -```json -{ - "managerPassword": "PASSWORD", - "music": true -} +```env + + WEB_ORIGIN=http://localhost:3000 + SOCKET_URL=http://localhost:3001 + MANAGER_PASSWORD=PASSWORD + MUSIC_ENABLED=false + ``` Options: -- `managerPassword`: The master password for accessing the manager interface -- `music`: Enable/disable game music +- `MANAGER_PASSWORD`: The master password for accessing the manager interface +- `MUSIC_ENABLED`: Enable/disable game music +- `WEB_ORIGIN`: Frontend URL +- `SOCKET_URL`: Websocket URL -### 2. Quiz Configuration (`config/quizz/*.json`) +### 2. Quiz Configuration (`quizz/*.json`) Create your quiz files in the `config/quizz/` directory. You can have multiple quiz files and select which one to use when starting a game. -Example quiz configuration (`config/quizz/example.json`): +Example quiz configuration (`quizz/example.json`): ```json { @@ -156,7 +169,7 @@ Quiz Options: ## 🎮 How to Play 1. Access the manager interface at http://localhost:3000/manager -2. Enter the manager password (defined in quiz config) +2. Enter the manager password (defined in env file) 3. Share the game URL (http://localhost:3000) and room code with participants 4. Wait for players to join 5. Click the start button to begin the game diff --git a/compose.yml b/compose.yml index 8db22591..45267724 100644 --- a/compose.yml +++ b/compose.yml @@ -7,7 +7,9 @@ services: - "3000:3000" - "3001:3001" volumes: - - ./config:/app/config # Path to your game config + - ./config:/app/quizz # Path to your quizz files environment: - - WEB_ORIGIN=http://localhost:3000 - - SOCKET_URL=http://localhost:3001 + - WEB_ORIGIN=${WEB_ORIGIN} + - SOCKET_URL=${SOCKET_URL} + - MANAGER_PASSWORD=${MANAGER_PASSWORD} + - MUSIC_ENABLED=${MUSIC_ENABLED} diff --git a/config/game.json b/config/game.json deleted file mode 100644 index 74e47a59..00000000 --- a/config/game.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "managerPassword": "PASSWORD" -} diff --git a/packages/socket/src/env.ts b/packages/socket/src/env.ts index 3be0d1d7..ccf9aea7 100644 --- a/packages/socket/src/env.ts +++ b/packages/socket/src/env.ts @@ -5,11 +5,15 @@ const env = createEnv({ server: { WEB_ORIGIN: z.string().optional().default("http://localhost:3000"), SOCKER_PORT: z.string().optional().default("3001"), + MANAGER_PASSWORD: z.string(), + MUSIC_ENABLED: z.enum(["true", "false"]).transform((v) => v === "true"), }, runtimeEnv: { WEB_ORIGIN: process.env.WEB_ORIGIN, SOCKER_PORT: process.env.SOCKER_PORT, + MANAGER_PASSWORD: process.env.MANAGER_PASSWORD, + MUSIC_ENABLED: process.env.MUSIC_ENABLED, }, }) diff --git a/packages/socket/src/services/config.ts b/packages/socket/src/services/config.ts index 92db8282..caa93c05 100644 --- a/packages/socket/src/services/config.ts +++ b/packages/socket/src/services/config.ts @@ -7,39 +7,22 @@ const inContainerPath = process.env.CONFIG_PATH const getPath = (path: string = "") => inContainerPath ? resolve(inContainerPath, path) - : resolve(process.cwd(), "../../config", path) + : resolve(process.cwd(), "../../quizz", path) + +type GameConfig = { + managerPassword: string + music: boolean +} class Config { static init() { - const isConfigFolderExists = fs.existsSync(getPath()) + const isQuizzFolderExists = fs.existsSync(getPath()) - if (!isConfigFolderExists) { + if (!isQuizzFolderExists) { fs.mkdirSync(getPath()) - } - - const isGameConfigExists = fs.existsSync(getPath("game.json")) - - if (!isGameConfigExists) { - fs.writeFileSync( - getPath("game.json"), - JSON.stringify( - { - managerPassword: "PASSWORD", - music: true, - }, - null, - 2 - ) - ) - } - - const isQuizzExists = fs.existsSync(getPath("quizz")) - - if (!isQuizzExists) { - fs.mkdirSync(getPath("quizz")) fs.writeFileSync( - getPath("quizz/example.json"), + getPath("example.json"), JSON.stringify( { subject: "Example Quizz", @@ -76,26 +59,18 @@ class Config { } } - static game() { - const isExists = fs.existsSync(getPath("game.json")) - - if (!isExists) { - throw new Error("Game config not found") - } - - try { - const config = fs.readFileSync(getPath("game.json"), "utf-8") + static game(): GameConfig { + const managerPassword = process.env.MANAGER_PASSWORD ?? "PASSWORD" + const musicEnv = process.env.MUSIC_ENABLED ?? "true" + const music = musicEnv.toLowerCase() === "true" - return JSON.parse(config) - } catch (error) { - console.error("Failed to read game config:", error) + return { + managerPassword, + music, } - - return {} } - static quizz() { - const isExists = fs.existsSync(getPath("quizz")) + const isExists = fs.existsSync(getPath()) if (!isExists) { return [] @@ -103,11 +78,11 @@ class Config { try { const files = fs - .readdirSync(getPath("quizz")) + .readdirSync(getPath()) .filter((file) => file.endsWith(".json")) const quizz: QuizzWithId[] = files.map((file) => { - const data = fs.readFileSync(getPath(`quizz/${file}`), "utf-8") + const data = fs.readFileSync(getPath(`${file}`), "utf-8") const config = JSON.parse(data) const id = file.replace(".json", "") diff --git a/config/quizz/example.json b/quizz/example.json similarity index 100% rename from config/quizz/example.json rename to quizz/example.json