2c8e568255a9eb4a5b1a3c689531304b3dacc739
checkAllPlayersReady() set the session to PLAYING and cleared/updated its timer in memory but never flushed, relying on callers to do so. The toggleReady() caller flushed right after, but GameController::index()'s lazy catch-up call did not - so if the "everyone ready" transition was only detected on a page load (e.g. players didn't click ready within the same 60s window), the terminal would render for that one request from the in-memory state, letting the game be played entirely through the unguarded message API, while the database silently kept the session on 'ready' with timer 0 forever. This made sessions invisible to the mainframe hint cron and the admin "running sessions" count. Moved the flush inside checkAllPlayersReady() itself so both callers persist reliably. Also chmod the logrotate configs after COPY in the Dockerfile, since their on-disk mode ended up group-writable (0664) depending on the build host's umask, which made logrotate refuse to use them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EscapePage — Online Escape Room
This repository contains a Symfony 7.3 (PHP >= 8.5.1) application for a collaborative online escape room experience.
- Start here: doc/FILES.md — quick file index.
- Development standards and workflows: doc/CONTRIBUTING.md
- All documentation is located under the doc/ directory.
Getting Started (brief)
-
Local machine (no Docker):
- Copy
.envto.env.localand set database andAPP_SECRET. - Install PHP deps:
composer install - Create database and run migrations (when present):
php bin/console doctrine:database:create --if-not-exists && php bin/console doctrine:migrations:migrate -n - Install/import JS deps:
php bin/console importmap:install - Run the server:
symfony server:start -d(or your preferred web server) - Run tests:
vendor/bin/phpunit
- Copy
-
With Docker:
cd docker && docker compose up -d- Install vendors inside the PHP container:
docker compose exec php bashcomposer install
- Initialize DB:
php bin/console doctrine:database:create --if-not-existsphp bin/console doctrine:migrations:migrate -n
- App is at http://localhost:8080
Email (Mailpit in dev, Mailgun for prod)
- Dev: a
mailerservice (Mailpit) runs in Docker.- SMTP DSN in
.env:MAILER_DSN=smtp://mailer:1025 - Mailpit UI: http://localhost:8025 (or mapped port 8025)
- Send a test mail:
php bin/console app:mail:test you@example.com
- SMTP DSN in
- Staging/Prod: use Mailgun.
- Require package (already in composer):
symfony/mailgun-mailer. - Set environment variables (do NOT commit secrets):
MAILER_DSN=mailgun+api://${MAILGUN_API_KEY}:${MAILGUN_DOMAIN}@default?region=euMAILGUN_API_KEY=YOUR_REAL_KEYMAILGUN_DOMAIN=YOUR_SENDING_DOMAIN(e.g.mg.escapepage.nl)- Optional:
MAILER_FROM=no-reply@your-domain.tld
- Drop
region=eu(or useregion=us) depending on which region your Mailgun domain was created in. - Alternatively via SMTP (no extra package):
MAILER_DSN="mailgun+smtp://USERNAME:PASSWORD@default?region=eu"
- Require package (already in composer):
Troubleshooting:
- If emails don’t appear in dev, open Mailpit at http://localhost:8025 and verify messages.
- In prod, check logs for HTTP 2xx responses from Mailgun and verify sender domain is verified (SPF/DKIM) in Mailgun.
Frontend assets with Webpack Encore
We use Webpack Encore to build and minify JS/CSS from the assets/ directory into public/build/.
Install Node dependencies (on your host machine):
npm install
Common commands:
# One-time dev build
npm run dev
# Watch & rebuild on changes
npm run watch
# Production build (minified, versioned filenames)
npm run build
How it’s wired:
- Entry file:
assets/app.js(importsassets/styles/app.css). - Webpack config:
webpack.config.jsoutputs topublic/build/. - Twig template includes built assets via Encore:
- In
templates/base.html.twig:{{ encore_entry_link_tags('app') }}(CSS){{ encore_entry_script_tags('app') }}(JS)
- In
Notes:
- The PHP Docker image does not include Node. Run the build commands on your host, or ask to add a Node build container if you prefer fully containerized builds.
- Built files are ignored by git except for
public/build/.gitignoreto keep the directory.
See doc/CONTRIBUTING.md for code style and more details.
Real‑time updates with Mercure
We use a Mercure hub (Docker service) to push server updates to browsers via Server‑Sent Events (SSE).
Quick start (dev):
- Start Docker stack:
This starts
cd docker && docker compose up -dmercureat http://localhost:8090 and the app at http://localhost:8080. - Install PHP deps inside the PHP container if you haven't yet:
You should see a console log like
docker compose exec php bash composer install[Mercure] Update received: { ... }on the Game Hub page.
Configuration:
- Env vars (defined in
.env, override in.env.localas needed):MERCURE_URL=http://mercure/.well-known/mercure(internal URL from PHP to the hub)MERCURE_PUBLIC_URL=http://localhost:8090/.well-known/mercure(browser URL)MERCURE_JWT_SECRET=!ChangeThisMercureJWT!(dev secret; do not use in prod)MERCURE_TOPIC_BASE=https://escapepage.dev(base topic URL)
- Docker service
mercureis based ondunglas/mercureand allows anonymous subscribers in dev.
Topics:
- Topics must be URLs. We use
MERCURE_TOPIC_BASEto control the domain per environment.- Dev:
.envsetshttps://escapepage.dev - Prod: set
MERCURE_TOPIC_BASE=https://escapepage.com
- Dev:
- The Game Hub demo topic is
${MERCURE_TOPIC_BASE}/game/hub.
Production notes:
- Disable anonymous subscribers and require JWTs for subscriptions (configure the Mercure container accordingly).
- Use a strong, rotated
MERCURE_JWT_SECRETand keep it out of VCS (use real env/secrets). - Serve Mercure over HTTPS and set proper CORS/allowed origins for your production domain(s).
Languages
PHP
59.1%
Twig
28.5%
JavaScript
8.5%
Shell
2.7%
CSS
0.7%
Other
0.5%