Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/node_modules
.env
.secrets
release.json
release.json
quizz/*
!quizz/example.json
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
"javascriptreact",
"typescript",
"typescriptreact"
]
],

"prettier.semi": false
}
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
41 changes: 27 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
3 changes: 0 additions & 3 deletions config/game.json

This file was deleted.

4 changes: 4 additions & 0 deletions packages/socket/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
})

Expand Down
63 changes: 19 additions & 44 deletions packages/socket/src/services/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -76,38 +59,30 @@ 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 []
}

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", "")
Expand Down
File renamed without changes.