Startup
This commit is contained in:
12
.env
12
.env
@@ -25,7 +25,6 @@ APP_SECRET=
|
|||||||
#
|
#
|
||||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
||||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
|
||||||
DATABASE_URL="mysql://app:!ChangeMe!@database:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
DATABASE_URL="mysql://app:!ChangeMe!@database:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
@@ -39,4 +38,15 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
|||||||
###> symfony/mailer ###
|
###> symfony/mailer ###
|
||||||
# Development: use Mailpit (docker compose override provides service `mailer` on port 1025)
|
# Development: use Mailpit (docker compose override provides service `mailer` on port 1025)
|
||||||
MAILER_DSN=smtp://mailer:1025
|
MAILER_DSN=smtp://mailer:1025
|
||||||
|
# Production/Stage (uncomment and set SENDGRID_API_KEY in real env or secrets):
|
||||||
|
# MAILER_DSN=sendgrid+api://%env(SENDGRID_API_KEY)%
|
||||||
|
# Alternatively, via SMTP (no extra package needed):
|
||||||
|
# MAILER_DSN="smtp://apikey:%env(SENDGRID_API_KEY)%@smtp.sendgrid.net:587?encryption=tls"
|
||||||
|
# Optional default sender (used by test command if --from not passed):
|
||||||
|
# MAILER_FROM=no-reply@your-domain.tld
|
||||||
|
# SENDGRID_API_KEY=your_real_key_goes_here # Do NOT commit this; set in .env.local or deployment env
|
||||||
###< symfony/mailer ###
|
###< symfony/mailer ###
|
||||||
|
|
||||||
|
###> symfony/sendgrid-mailer ###
|
||||||
|
# MAILER_DSN=sendgrid://KEY@default
|
||||||
|
###< symfony/sendgrid-mailer ###
|
||||||
|
|||||||
9
.env.prod
Normal file
9
.env.prod
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
### Compiled or real environment variables should be used in production.
|
||||||
|
### Configure MAILER_DSN to use SendGrid API transport.
|
||||||
|
### Prefer storing SENDGRID_API_KEY using Symfony Secrets or real env vars.
|
||||||
|
|
||||||
|
###> symfony/mailer ###
|
||||||
|
# Example using SendGrid API key (replace with real secret via vault/secrets):
|
||||||
|
# SENDGRID_API_KEY=SG.xxxxx
|
||||||
|
MAILER_DSN=sendgrid+api://%env(resolve:SENDGRID_API_KEY)%@default
|
||||||
|
###< symfony/mailer ###
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -18,3 +18,10 @@
|
|||||||
/public/assets/
|
/public/assets/
|
||||||
/assets/vendor/
|
/assets/vendor/
|
||||||
###< symfony/asset-mapper ###
|
###< symfony/asset-mapper ###
|
||||||
|
|
||||||
|
###> symfony/webpack-encore-bundle ###
|
||||||
|
/node_modules/
|
||||||
|
/public/build/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
###< symfony/webpack-encore-bundle ###
|
||||||
|
|||||||
2
.idea/escapepage.iml
generated
2
.idea/escapepage.iml
generated
@@ -133,6 +133,8 @@
|
|||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/extra-bundle" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/extra-bundle" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/sendgrid-mailer" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/webpack-encore-bundle" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
2
.idea/php.xml
generated
2
.idea/php.xml
generated
@@ -146,6 +146,8 @@
|
|||||||
<path value="$PROJECT_DIR$/vendor/monolog/monolog" />
|
<path value="$PROJECT_DIR$/vendor/monolog/monolog" />
|
||||||
<path value="$PROJECT_DIR$/vendor/masterminds/html5" />
|
<path value="$PROJECT_DIR$/vendor/masterminds/html5" />
|
||||||
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
|
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
|
||||||
|
<path value="$PROJECT_DIR$/vendor/symfony/sendgrid-mailer" />
|
||||||
|
<path value="$PROJECT_DIR$/vendor/symfony/webpack-encore-bundle" />
|
||||||
</include_path>
|
</include_path>
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.2">
|
<component name="PhpProjectSharedConfiguration" php_language_level="8.2">
|
||||||
|
|||||||
78
README.md
Normal file
78
README.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# EscapePage — Online Escape Room
|
||||||
|
|
||||||
|
This repository contains a Symfony 7.3 (PHP >= 8.2) 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):
|
||||||
|
1. Copy `.env` to `.env.local` and set database and `APP_SECRET`.
|
||||||
|
2. Install PHP deps: `composer install`
|
||||||
|
3. Create database and run migrations (when present): `php bin/console doctrine:database:create --if-not-exists && php bin/console doctrine:migrations:migrate -n`
|
||||||
|
4. Install/import JS deps: `php bin/console importmap:install`
|
||||||
|
5. Run the server: `symfony server:start -d` (or your preferred web server)
|
||||||
|
6. Run tests: `vendor/bin/phpunit`
|
||||||
|
|
||||||
|
- With Docker:
|
||||||
|
1. From `docker/`: `docker compose up -d`
|
||||||
|
2. Install vendors inside the PHP container:
|
||||||
|
- `docker compose exec php bash`
|
||||||
|
- `composer install`
|
||||||
|
3. Initialize DB:
|
||||||
|
- `php bin/console doctrine:database:create --if-not-exists`
|
||||||
|
- `php bin/console doctrine:migrations:migrate -n`
|
||||||
|
4. App is at http://localhost:8080
|
||||||
|
|
||||||
|
## Email (Mailpit in dev, SendGrid for prod)
|
||||||
|
- Dev: a `mailer` service (Mailpit) runs in Docker.
|
||||||
|
- SMTP DSN in `.env`: `MAILER_DSN=smtp://mailer:1025`
|
||||||
|
- Mailpit UI: http://localhost:8025
|
||||||
|
- Send a test mail: `php bin/console app:mail:test you@example.com`
|
||||||
|
- Staging/Prod: use SendGrid.
|
||||||
|
- Require package (already in composer): `symfony/sendgrid-mailer`.
|
||||||
|
- Set environment variables (do NOT commit secrets):
|
||||||
|
- `MAILER_DSN=sendgrid+api://%env(SENDGRID_API_KEY)%`
|
||||||
|
- `SENDGRID_API_KEY=YOUR_REAL_KEY`
|
||||||
|
- Optional: `MAILER_FROM=no-reply@your-domain.tld`
|
||||||
|
- Alternatively via SMTP (no extra package):
|
||||||
|
- `MAILER_DSN="smtp://apikey:%env(SENDGRID_API_KEY)%@smtp.sendgrid.net:587?encryption=tls"`
|
||||||
|
|
||||||
|
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 SendGrid and verify sender domain is verified in SendGrid.
|
||||||
|
|
||||||
|
## 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` (imports `assets/styles/app.css`).
|
||||||
|
- Webpack config: `webpack.config.js` outputs to `public/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)
|
||||||
|
|
||||||
|
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/.gitignore` to keep the directory.
|
||||||
|
|
||||||
|
See doc/CONTRIBUTING.md for code style and more details.
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import './bootstrap.js';
|
|
||||||
/*
|
/*
|
||||||
* Welcome to your app's main JavaScript file!
|
* Welcome to your app's main JavaScript file!
|
||||||
*
|
|
||||||
* This file will be included onto the page via the importmap() Twig function,
|
|
||||||
* which should already be in your base.html.twig.
|
|
||||||
*/
|
*/
|
||||||
import './styles/app.css';
|
import './styles/app.css';
|
||||||
|
|
||||||
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
|
console.log('This log comes from assets/app.js built by Webpack Encore! 🎉');
|
||||||
|
|||||||
11
assets/game1.js
Normal file
11
assets/game1.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* Game1 entry point built with Webpack Encore */
|
||||||
|
import './styles/game1.css';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Simple boot log so you can verify it in the browser console
|
||||||
|
// and confirm this specific bundle is loaded on the Game Hub page.
|
||||||
|
console.log('Game1 bundle loaded');
|
||||||
|
|
||||||
|
// Example: add a CSS class to <body> so page-specific styles can apply
|
||||||
|
document.body.classList.add('game1-page');
|
||||||
|
});
|
||||||
17
assets/styles/game1.css
Normal file
17
assets/styles/game1.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* Styles specific to Game1 */
|
||||||
|
|
||||||
|
/* page-level indicator to confirm CSS is loaded */
|
||||||
|
body.game1-page {
|
||||||
|
/* subtle background tint so you can visually confirm on /game */
|
||||||
|
background-color: #f9fbff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* example component style */
|
||||||
|
.game1-banner {
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border: 1px solid #cfe2ff;
|
||||||
|
background: #e9f2ff;
|
||||||
|
color: #0b5ed7;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
0
bin/console
Executable file → Normal file
0
bin/console
Executable file → Normal file
0
bin/phpunit
Executable file → Normal file
0
bin/phpunit
Executable file → Normal file
@@ -26,6 +26,7 @@
|
|||||||
"symfony/intl": "7.3.*",
|
"symfony/intl": "7.3.*",
|
||||||
"symfony/mailer": "7.3.*",
|
"symfony/mailer": "7.3.*",
|
||||||
"symfony/mime": "7.3.*",
|
"symfony/mime": "7.3.*",
|
||||||
|
"symfony/sendgrid-mailer": "7.3.*",
|
||||||
"symfony/monolog-bundle": "^3.0",
|
"symfony/monolog-bundle": "^3.0",
|
||||||
"symfony/notifier": "7.3.*",
|
"symfony/notifier": "7.3.*",
|
||||||
"symfony/process": "7.3.*",
|
"symfony/process": "7.3.*",
|
||||||
@@ -43,7 +44,8 @@
|
|||||||
"symfony/web-link": "7.3.*",
|
"symfony/web-link": "7.3.*",
|
||||||
"symfony/yaml": "7.3.*",
|
"symfony/yaml": "7.3.*",
|
||||||
"twig/extra-bundle": "^2.12|^3.0",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
"twig/twig": "^2.12|^3.0"
|
"twig/twig": "^2.12|^3.0",
|
||||||
|
"symfony/webpack-encore-bundle": "^2.1"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
|||||||
5964
composer.lock
generated
5964
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -13,4 +13,5 @@ return [
|
|||||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
|
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ doctrine:
|
|||||||
App:
|
App:
|
||||||
type: attribute
|
type: attribute
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
dir: '%kernel.project_dir%/src/Entity'
|
dir: '%kernel.project_dir%/src'
|
||||||
prefix: 'App\Entity'
|
prefix: 'App'
|
||||||
alias: App
|
alias: App
|
||||||
controller_resolver:
|
controller_resolver:
|
||||||
auto_mapping: false
|
auto_mapping: false
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ framework:
|
|||||||
notifier:
|
notifier:
|
||||||
chatter_transports:
|
chatter_transports:
|
||||||
texter_transports:
|
texter_transports:
|
||||||
|
sendgrid: '%env(MAILER_DSN)%'
|
||||||
channel_policy:
|
channel_policy:
|
||||||
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||||
urgent: ['email']
|
urgent: ['email']
|
||||||
|
|||||||
45
config/packages/webpack_encore.yaml
Normal file
45
config/packages/webpack_encore.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
webpack_encore:
|
||||||
|
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
|
||||||
|
output_path: '%kernel.project_dir%/public/build'
|
||||||
|
# If multiple builds are defined (as shown below), you can disable the default build:
|
||||||
|
# output_path: false
|
||||||
|
|
||||||
|
# Set attributes that will be rendered on all script and link tags
|
||||||
|
script_attributes:
|
||||||
|
defer: true
|
||||||
|
# Uncomment (also under link_attributes) if using Turbo Drive
|
||||||
|
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
|
||||||
|
# 'data-turbo-track': reload
|
||||||
|
# link_attributes:
|
||||||
|
# Uncomment if using Turbo Drive
|
||||||
|
# 'data-turbo-track': reload
|
||||||
|
|
||||||
|
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
|
||||||
|
# crossorigin: 'anonymous'
|
||||||
|
|
||||||
|
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
|
||||||
|
# preload: true
|
||||||
|
|
||||||
|
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
||||||
|
# strict_mode: false
|
||||||
|
|
||||||
|
# If you have multiple builds:
|
||||||
|
# builds:
|
||||||
|
# frontend: '%kernel.project_dir%/public/frontend/build'
|
||||||
|
|
||||||
|
# pass the build name as the 3rd argument to the Twig functions
|
||||||
|
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
||||||
|
|
||||||
|
framework:
|
||||||
|
assets:
|
||||||
|
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
|
||||||
|
|
||||||
|
#when@prod:
|
||||||
|
# webpack_encore:
|
||||||
|
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||||
|
# # Available in version 1.2
|
||||||
|
# cache: true
|
||||||
|
|
||||||
|
#when@test:
|
||||||
|
# webpack_encore:
|
||||||
|
# strict_mode: false
|
||||||
@@ -1,5 +1,23 @@
|
|||||||
controllers:
|
# Attribute-based routing imports for controllers in subnamespaces
|
||||||
|
|
||||||
|
website_controllers:
|
||||||
resource:
|
resource:
|
||||||
path: ../src/Controller/
|
path: ../src/Website/Controller/
|
||||||
namespace: App\Controller
|
namespace: App\Website\Controller
|
||||||
type: attribute
|
type: attribute
|
||||||
|
|
||||||
|
game_controllers:
|
||||||
|
resource:
|
||||||
|
path: ../src/Game/Controller/
|
||||||
|
namespace: App\Game\Controller
|
||||||
|
type: attribute
|
||||||
|
prefix: /game
|
||||||
|
|
||||||
|
# Uncomment when you add base controllers
|
||||||
|
# base_controllers:
|
||||||
|
# resource:
|
||||||
|
# path: ../src/Base/Controller/
|
||||||
|
# namespace: App\Base\Controller
|
||||||
|
# type: attribute
|
||||||
|
# # Set a prefix if desired, e.g., "/base" or leave empty to mount at root
|
||||||
|
# # prefix: /base
|
||||||
|
|||||||
13
config/routes/app.yaml
Normal file
13
config/routes/app.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
website:
|
||||||
|
resource: ../../src/Website/Controller/
|
||||||
|
type: attribute
|
||||||
|
prefix:
|
||||||
|
en: '/'
|
||||||
|
nl: '/nl'
|
||||||
|
|
||||||
|
game:
|
||||||
|
resource: ../../src/Game/Controller/
|
||||||
|
type: attribute
|
||||||
|
prefix:
|
||||||
|
en: '/game'
|
||||||
|
nl: '/nl/game'
|
||||||
138
doc/CONTRIBUTING.md
Normal file
138
doc/CONTRIBUTING.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# Contribution & Code Style Guide
|
||||||
|
|
||||||
|
This document is for Junie (and humans) to keep our code style consistent and to quickly find the files we’ll reference during development of the Online Escape Room platform.
|
||||||
|
|
||||||
|
Project type: Symfony 7.3, PHP >= 8.2, Doctrine ORM 3, Twig, Stimulus (UX), Importmap/Asset Mapper, PHPUnit 11.
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Repository Conventions
|
||||||
|
- PHP version: 8.2+ (composer.json enforces ">=8.2").
|
||||||
|
- Framework: Symfony 7.3.* (see composer.json).
|
||||||
|
- Architecture: MVC with Controllers in `src/Controller`, Entities in `src/Entity`, Repositories in `src/Repository`, Templates in `templates`.
|
||||||
|
- Env files: `.env`, `.env.local` (ignored), and environment overrides like `.env.test.local`.
|
||||||
|
- Routes: annotation/attributes in controllers or YAML/PHP in `config/routes`.
|
||||||
|
- Documentation location: All documentation must live under the `doc/` directory. New docs requested by the team will be added there.
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Coding Standards
|
||||||
|
|
||||||
|
### 2.1 PHP
|
||||||
|
- Standard: PSR-12 (line length soft cap 120; prefer multi-line for long signatures/arrays).
|
||||||
|
- Strict types at top of new files: `declare(strict_types=1);`
|
||||||
|
- Type-hint everything (params, returns, properties). Prefer readonly where applicable.
|
||||||
|
- Visibility: declare on all properties and methods.
|
||||||
|
- Exceptions: throw domain-specific exceptions for game logic; do not return null where an exception is appropriate.
|
||||||
|
- Controllers: keep thin; delegate to services. Use DTOs/Form types to validate input.
|
||||||
|
- Dependency Injection: constructor injection; avoid container-aware services.
|
||||||
|
- Naming: Services suffixed with `...Service`, commands with `...Command`, event listeners with `...Listener`/`...Subscriber`.
|
||||||
|
- Logging: use `Psr\Log\LoggerInterface` where meaningful.
|
||||||
|
|
||||||
|
### 2.2 Twig
|
||||||
|
- Keep templates lean; no heavy logic. Use filters/functions and view models if needed.
|
||||||
|
- Use `trans` for strings that will be localized.
|
||||||
|
- Partial templates/components: place in `templates/_partials` or `templates/components`.
|
||||||
|
|
||||||
|
### 2.3 JavaScript (Stimulus / vanilla)
|
||||||
|
- Use ES modules via Importmap/Asset Mapper.
|
||||||
|
- Controllers in `assets/controllers`. Name as `something_controller.js` following Stimulus conventions.
|
||||||
|
- Prefer small, focused controllers. Keep DOM queries scoped to controller element.
|
||||||
|
- Avoid global state; communicate via events or Turbo streams when appropriate.
|
||||||
|
|
||||||
|
### 2.4 Styles (CSS/SCSS)
|
||||||
|
- Keep styles in `assets/styles`. Use BEM naming for classes.
|
||||||
|
- Prefer CSS variables for theme colors, spacing scale, z-index scale.
|
||||||
|
|
||||||
|
### 2.5 YAML / Config
|
||||||
|
- 2-space indentation, no tabs.
|
||||||
|
- Use parameters and env vars (`env()`) instead of hardcoding secrets.
|
||||||
|
|
||||||
|
### 2.6 Git & Commits
|
||||||
|
- Branch naming: `feature/<short-name>`, `fix/<short-name>`, `chore/<short-name>`.
|
||||||
|
- Commit messages:
|
||||||
|
- Conventional commits style: `feat: ...`, `fix: ...`, `chore: ...`, `docs: ...`, `test: ...`, `refactor: ...`.
|
||||||
|
- First line ≤ 72 chars; add body when needed with rationale.
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Linting, Tests, and Quality
|
||||||
|
|
||||||
|
### 3.1 PHP
|
||||||
|
- Use PHP-CS-Fixer or PHP_CodeSniffer (PHPCS) with PSR-12. If not yet installed, proposed composer scripts (to add later):
|
||||||
|
- `composer require --dev friendsofphp/php-cs-fixer`
|
||||||
|
- Script: `php-cs-fixer fix --allow-risky=yes`
|
||||||
|
- Static analysis: PHPStan level 6–8 recommended:
|
||||||
|
- `composer require --dev phpstan/phpstan`
|
||||||
|
- Run: `vendor/bin/phpstan analyse src tests` (configure `phpstan.neon` later)
|
||||||
|
|
||||||
|
### 3.2 Symfony
|
||||||
|
- Cache and debug:
|
||||||
|
- `php bin/console cache:clear`
|
||||||
|
- `php bin/console debug:router`
|
||||||
|
- `php bin/console debug:container`
|
||||||
|
|
||||||
|
### 3.3 Tests
|
||||||
|
- PHPUnit (already required):
|
||||||
|
- Run tests: `vendor/bin/phpunit`
|
||||||
|
- Tests location: `tests/`
|
||||||
|
- Config: `phpunit.dist.xml`
|
||||||
|
|
||||||
|
### 3.4 Frontend
|
||||||
|
- Stimulus/UX: controllers auto-registered via `symfony/stimulus-bundle`.
|
||||||
|
- Asset Mapper/Importmap:
|
||||||
|
- Install deps: `php bin/console importmap:install`
|
||||||
|
- Dev server (if using symfony local server): `symfony server:start -d`
|
||||||
|
|
||||||
|
|
||||||
|
## 4. Project File Map (Quick Reference)
|
||||||
|
- App kernel: `src/Kernel.php`
|
||||||
|
- Controllers: `src/Controller/`
|
||||||
|
- Domain entities: `src/Entity/`
|
||||||
|
- Repositories: `src/Repository/`
|
||||||
|
- Migrations: `migrations/`
|
||||||
|
- Templates: `templates/`
|
||||||
|
- Translations: `translations/`
|
||||||
|
- Assets entry: `assets/app.js`, styles in `assets/styles/`
|
||||||
|
- Stimulus controllers: `assets/controllers/`
|
||||||
|
- Routes: `config/routes/`
|
||||||
|
- Packages config: `config/packages/`
|
||||||
|
- Env vars: `.env` (base), `.env.local` (local overrides)
|
||||||
|
- Public web root: `public/`
|
||||||
|
- Tests: `tests/`
|
||||||
|
|
||||||
|
|
||||||
|
## 5. How We Build Features (Checklist)
|
||||||
|
1) Create branch: `feature/<name>`.
|
||||||
|
2) Describe the task in an issue with acceptance criteria.
|
||||||
|
3) Implement backend (entities/services/controllers) with tests.
|
||||||
|
4) Implement templates and Stimulus controller if interactive.
|
||||||
|
5) Add/adjust routes and translations.
|
||||||
|
6) Run: linters, phpstan, phpunit; ensure green.
|
||||||
|
7) Open PR; request review.
|
||||||
|
|
||||||
|
|
||||||
|
## 6. Escape Room Domain Notes (early)
|
||||||
|
- Core concepts likely: Game, Room, Puzzle, Session, Player, Team, Hint, Timer.
|
||||||
|
- Consider events (Domain Events) for puzzle solved, hint requested, time warnings.
|
||||||
|
- Real-time collaboration options: Symfony Mercure, WebSockets, or Turbo Streams.
|
||||||
|
- Persist immutable audit trail for gameplay.
|
||||||
|
|
||||||
|
|
||||||
|
## 7. Editor Configuration
|
||||||
|
- Recommend EditorConfig. If we add `.editorconfig` later, set:
|
||||||
|
- Indent 4 spaces for PHP, 2 for YAML/Twig/JS/CSS.
|
||||||
|
- LF line endings, UTF-8, insert final newline, trim trailing whitespace.
|
||||||
|
|
||||||
|
|
||||||
|
## 8. Security & Secrets
|
||||||
|
- Never commit secrets. Use environment variables or Symfony Vault.
|
||||||
|
- Review `APP_ENV`, `APP_SECRET`, database DSN in `.env` and `.env.local`.
|
||||||
|
|
||||||
|
|
||||||
|
## 9. Release & Environments
|
||||||
|
- Envs: `dev`, `test`, `prod`.
|
||||||
|
- Build steps: run migrations, warmup cache, compile assets if using.
|
||||||
|
|
||||||
|
|
||||||
|
## 10. References
|
||||||
|
- Symfony Best Practices: https://symfony.com/doc/current/best_practices.html
|
||||||
|
- Doctrine ORM 3 Docs: https://www.doctrine-project.org/projects/doctrine-orm/en/current/
|
||||||
|
- Stimulus: https://stimulus.hotwired.dev/
|
||||||
51
doc/FILES.md
Normal file
51
doc/FILES.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Project File Index (Quick Reference)
|
||||||
|
|
||||||
|
Use this index to quickly locate files and directories during development and in discussions.
|
||||||
|
|
||||||
|
## Top-Level
|
||||||
|
- docker/compose.yaml / docker/compose.override.yaml — Docker services.
|
||||||
|
- docker/ — Docker build contexts and configs (php Dockerfile, nginx vhost, compose files).
|
||||||
|
- composer.json / composer.lock — Dependencies and scripts.
|
||||||
|
- importmap.php — Importmap configuration for JS dependencies.
|
||||||
|
- phpunit.dist.xml — PHPUnit configuration.
|
||||||
|
- public/ — Web root (index.php, assets, static files).
|
||||||
|
- var/ — Cache and logs.
|
||||||
|
- vendor/ — Composer dependencies.
|
||||||
|
|
||||||
|
## Application Source (src/)
|
||||||
|
- src/Kernel.php — Symfony Kernel bootstrapping.
|
||||||
|
- src/Website/ — Marketing/public website area (controllers, templates under templates/website/).
|
||||||
|
- src/Game/ — Game area (controllers, templates under templates/game/).
|
||||||
|
- src/Entity/ — Doctrine ORM entities.
|
||||||
|
- src/Repository/ — Doctrine repositories.
|
||||||
|
|
||||||
|
## Configuration (config/)
|
||||||
|
- config/packages/ — Symfony bundles configuration (framework.yaml, cache.yaml, etc.).
|
||||||
|
- config/routes/ — Routing configuration files.
|
||||||
|
- config/routes/app.yaml — Imports attribute routes for both sites (Website and Game).
|
||||||
|
|
||||||
|
## Data & DB
|
||||||
|
- migrations/ — Doctrine migrations.
|
||||||
|
|
||||||
|
## Presentation
|
||||||
|
- templates/ — Twig templates.
|
||||||
|
- templates/website — Views for the public website.
|
||||||
|
- templates/game — Views for the game area.
|
||||||
|
- translations/ — i18n message files.
|
||||||
|
|
||||||
|
## Frontend Assets
|
||||||
|
- assets/app.js — Main JS entry (imports Stimulus, styles, etc.).
|
||||||
|
- assets/controllers/ — Stimulus controllers.
|
||||||
|
- assets/styles/ — Global styles.
|
||||||
|
- assets/vendor/ — Vendor frontend assets if any.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- tests/ — Test suites for PHPUnit.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
- .env — Base environment configuration.
|
||||||
|
- .env.local — Local overrides (ignored).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Follow doc/CONTRIBUTING.md for code style and workflows.
|
||||||
|
- Email setup: see doc/email.md for dev Mailpit and production SendGrid configuration.
|
||||||
10
doc/README.md
Normal file
10
doc/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Project Documentation
|
||||||
|
|
||||||
|
This directory contains all project documentation for EscapePage.
|
||||||
|
|
||||||
|
- Code Style & Contribution Guide: CONTRIBUTING.md
|
||||||
|
- Project File Index: FILES.md
|
||||||
|
|
||||||
|
Policy: All new and existing documentation must be placed in this doc/ directory. If you ask Junie to add docs in the future, they will be created under doc/.
|
||||||
|
|
||||||
|
Additional docs can be added here as the project grows (architecture decisions, API docs, gameplay design, onboarding, etc.).
|
||||||
60
doc/docker.md
Normal file
60
doc/docker.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Docker Setup
|
||||||
|
|
||||||
|
This app can run fully in Docker using docker compose with PHP-FPM, Nginx and MySQL.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
- php: PHP 8.2 FPM with required extensions and Composer
|
||||||
|
- nginx: Serves the Symfony app from public/ and proxies PHP to php-fpm
|
||||||
|
- database: MySQL 8.0 (data persisted in a volume)
|
||||||
|
- mailer (dev only via compose.override.yaml): Mailpit (SMTP/UI)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Docker and Docker Compose (v2)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### 1) Build and start
|
||||||
|
```
|
||||||
|
./docker/setup.sh
|
||||||
|
```
|
||||||
|
App will be served at http://localhost:8080
|
||||||
|
|
||||||
|
Alternatively (manual):
|
||||||
|
```
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Install dependencies
|
||||||
|
The setup script already runs composer install. To run manually:
|
||||||
|
```
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml exec php composer install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) Prepare DB
|
||||||
|
The setup script already prepares the DB. To run manually:
|
||||||
|
```
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml exec php php bin/console doctrine:database:create --if-not-exists
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml exec php php bin/console doctrine:migrations:migrate -n
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4) Run tests
|
||||||
|
```
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml exec php vendor/bin/phpunit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5) Logs
|
||||||
|
```
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml logs -f nginx
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml logs -f php
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6) Stop
|
||||||
|
```
|
||||||
|
docker compose -f docker/compose.yaml -f docker/compose.override.yaml down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- .env already points DATABASE_URL to the `database` service hostname.
|
||||||
|
- Nginx listens on port 8080 (mapped from container 80) to avoid conflicts.
|
||||||
|
- Source is bind-mounted; changes on host reflect inside containers.
|
||||||
|
- For production images, create a separate Dockerfile with build steps (composer install --no-dev, cache warmup) and avoid bind mounts.
|
||||||
45
doc/email.md
Normal file
45
doc/email.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Email Delivery: Dev Mailcatcher & Production SendGrid
|
||||||
|
|
||||||
|
This application uses Symfony Mailer. We separate development and production delivery:
|
||||||
|
|
||||||
|
- Development: Mailpit (mailcatcher) via SMTP in Docker.
|
||||||
|
- Production: SendGrid via API transport.
|
||||||
|
|
||||||
|
## Development (Mailpit)
|
||||||
|
|
||||||
|
- Service is defined in `compose.override.yaml` as `mailer` (axllent/mailpit).
|
||||||
|
- Ports:
|
||||||
|
- SMTP: 1025 (mapped to host 1025)
|
||||||
|
- Web UI: 8025 (mapped to host 8025)
|
||||||
|
- Default DSN for dev is set in `.env`:
|
||||||
|
|
||||||
|
```
|
||||||
|
MAILER_DSN=smtp://mailer:1025
|
||||||
|
```
|
||||||
|
|
||||||
|
- Usage:
|
||||||
|
1. Start stack: `docker compose up -d`
|
||||||
|
2. Send an email from the app.
|
||||||
|
3. Open http://localhost:8025 to view captured emails.
|
||||||
|
|
||||||
|
## Production (SendGrid)
|
||||||
|
|
||||||
|
Use the SendGrid API transport. Do not commit secrets.
|
||||||
|
|
||||||
|
- Example configuration is in `.env.prod`:
|
||||||
|
|
||||||
|
```
|
||||||
|
MAILER_DSN=sendgrid+api://%env(resolve:SENDGRID_API_KEY)%@default
|
||||||
|
```
|
||||||
|
|
||||||
|
- Provide `SENDGRID_API_KEY` via:
|
||||||
|
- Real environment variable on the server/container, or
|
||||||
|
- Symfony secrets: `php bin/console secrets:set SENDGRID_API_KEY` (and dump for prod), or
|
||||||
|
- Orchestration secret stores (e.g., Docker/K8s).
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- No Mailpit container is defined in the base `compose.yaml`, only in `compose.override.yaml`. This ensures it is used in development only.
|
||||||
|
- To test email locally without Docker, you can:
|
||||||
|
- Run Mailpit on your host (ports 1025/8025) and set `MAILER_DSN=smtp://127.0.0.1:1025` in `.env.local`.
|
||||||
|
- If you need to use SendGrid SMTP instead of API, a DSN example:
|
||||||
|
`smtp://apikey:YOUR_SENDGRID_API_KEY@smtp.sendgrid.net:587`.
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
|
|
||||||
|
version: '3.7'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
php:
|
php:
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: php/Dockerfile
|
dockerfile: php/Dockerfile
|
||||||
|
container_name: escapepage-php
|
||||||
volumes:
|
volumes:
|
||||||
- ../:/var/www/html:delegated
|
- ../:/var/www/html:delegated
|
||||||
environment:
|
environment:
|
||||||
APP_ENV: dev
|
APP_ENV: dev
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:1.27-alpine
|
image: nginx:1.27-alpine
|
||||||
|
container_name: escapepage-nginx
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -20,26 +27,50 @@ services:
|
|||||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- php
|
- php
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
mailer:
|
||||||
|
image: axllent/mailpit:latest
|
||||||
|
container_name: escapepage-mailer
|
||||||
|
ports:
|
||||||
|
- "8025:8025"
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
database:
|
database:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
|
container_name: escapepage-db
|
||||||
environment:
|
environment:
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
|
||||||
MYSQL_USER: ${MYSQL_USER:-app}
|
MYSQL_USER: ${MYSQL_USER:-app}
|
||||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-!ChangeMe!}
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-!ChangeMe!}
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
|
||||||
command: ["--default-authentication-plugin=mysql_native_password", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root}"]
|
||||||
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
start_period: 60s
|
start_period: 30s
|
||||||
|
command: ["--default-authentication-plugin=mysql_native_password", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
|
||||||
volumes:
|
volumes:
|
||||||
- database_data:/var/lib/mysql:rw
|
- database_data:/var/lib/mysql:rw
|
||||||
|
# Uncomment the two lines below if you need to access MySQL from your host (workbench, etc.)
|
||||||
|
# ports:
|
||||||
|
# - "3306:3306"
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
database_data:
|
database_data:
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
driver: bridge
|
||||||
|
|||||||
28
docker/nginx/default.conf
Normal file
28
docker/nginx/default.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /var/www/html/public;
|
||||||
|
index index.php index.html;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.php$is_args$args;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
fastcgi_param DOCUMENT_ROOT $realpath_root;
|
||||||
|
fastcgi_pass php:9000;
|
||||||
|
fastcgi_read_timeout 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_max_body_size 32m;
|
||||||
|
}
|
||||||
21
docker/php/Dockerfile
Normal file
21
docker/php/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM php:8.2-fpm-alpine
|
||||||
|
|
||||||
|
# Install system deps
|
||||||
|
RUN apk add --no-cache bash git icu-dev libzip-dev oniguruma-dev
|
||||||
|
|
||||||
|
# Install PHP extensions
|
||||||
|
RUN docker-php-ext-configure intl \
|
||||||
|
&& docker-php-ext-install -j$(nproc) intl pdo pdo_mysql opcache
|
||||||
|
|
||||||
|
# Install composer
|
||||||
|
ENV COMPOSER_ALLOW_SUPERUSER=1 \
|
||||||
|
COMPOSER_HOME=/tmp/composer
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# Configure PHP
|
||||||
|
COPY php.ini $PHP_INI_DIR/conf.d/zz-custom.ini
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
# Default command
|
||||||
|
CMD ["php-fpm"]
|
||||||
9
docker/php/php.ini
Normal file
9
docker/php/php.ini
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
memory_limit=512M
|
||||||
|
post_max_size=32M
|
||||||
|
upload_max_filesize=32M
|
||||||
|
max_execution_time=60
|
||||||
|
; For Symfony dev
|
||||||
|
opcache.enable=1
|
||||||
|
opcache.enable_cli=1
|
||||||
|
opcache.validate_timestamps=1
|
||||||
|
opcache.revalidate_freq=0
|
||||||
126
docker/setup.sh
Normal file
126
docker/setup.sh
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Simple setup script to bootstrap the Dockerized dev stack for this project.
|
||||||
|
# - Builds and starts containers (php, nginx, mariadb, mailpit via override)
|
||||||
|
# - Installs composer dependencies
|
||||||
|
# - Ensures APP_SECRET is set (generates if empty)
|
||||||
|
# - Creates and migrates the database
|
||||||
|
# - Installs/imports JS dependencies
|
||||||
|
# - Prints helpful info on success
|
||||||
|
|
||||||
|
# Usage:
|
||||||
|
# ./docker/setup.sh # full setup
|
||||||
|
# ./docker/setup.sh --no-build # skip image rebuild
|
||||||
|
# ./docker/setup.sh --down # stop and remove containers (down)
|
||||||
|
# ./docker/setup.sh --recreate # force recreate containers
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")"/.. && pwd)
|
||||||
|
DOCKER_DIR="$ROOT_DIR/docker"
|
||||||
|
# Determine the docker compose command (V2 'docker compose' or V1 'docker-compose')
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
DOCKER_COMPOSE="docker compose"
|
||||||
|
elif command -v docker-compose >/dev/null 2>&1; then
|
||||||
|
DOCKER_COMPOSE="docker-compose"
|
||||||
|
else
|
||||||
|
echo "Error: Neither 'docker compose' nor 'docker-compose' was found. Please install Docker Compose." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Helper to run docker compose from the docker/ directory
|
||||||
|
dc() { (cd "$DOCKER_DIR" && $DOCKER_COMPOSE -f compose.yaml "$@"); }
|
||||||
|
|
||||||
|
REBUILD=1
|
||||||
|
RECREATE=0
|
||||||
|
DOWN_ONLY=0
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--no-build) REBUILD=0 ;;
|
||||||
|
--recreate) RECREATE=1 ;;
|
||||||
|
--down) DOWN_ONLY=1 ;;
|
||||||
|
*) echo "Unknown option: $arg" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
need() { command -v "$1" >/dev/null 2>&1 || { echo "Error: '$1' is required but not installed." >&2; exit 1; }; }
|
||||||
|
|
||||||
|
need docker
|
||||||
|
|
||||||
|
if [ "$DOWN_ONLY" -eq 1 ]; then
|
||||||
|
dc down
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILD_ARGS=()
|
||||||
|
if [ "$REBUILD" -eq 1 ]; then
|
||||||
|
BUILD_ARGS+=("--build")
|
||||||
|
fi
|
||||||
|
if [ "$RECREATE" -eq 1 ]; then
|
||||||
|
BUILD_ARGS+=("--force-recreate")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start stack
|
||||||
|
dc up "${BUILD_ARGS[@]}"
|
||||||
|
|
||||||
|
# Helper to run commands in php container
|
||||||
|
pexec() { dc exec -T php "$@"; }
|
||||||
|
|
||||||
|
# Wait for database to be healthy (mariadb)
|
||||||
|
printf "Waiting for database to be healthy..."
|
||||||
|
# Use docker inspect health status
|
||||||
|
DB_HEALTH=""
|
||||||
|
for i in {1..60}; do
|
||||||
|
DB_HEALTH=$(docker inspect -f '{{.State.Health.Status}}' "$(docker ps --filter name=_database_ --format '{{.ID}}' | head -n1)" 2>/dev/null || true)
|
||||||
|
if [ "$DB_HEALTH" = "healthy" ]; then
|
||||||
|
echo " OK"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
printf "."
|
||||||
|
sleep 2
|
||||||
|
if [ "$i" -eq 60 ]; then
|
||||||
|
echo "\nWarning: database health check not healthy yet, continuing anyway."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Ensure composer is available and install dependencies
|
||||||
|
pexec composer install --no-interaction
|
||||||
|
|
||||||
|
# Ensure APP_SECRET is set
|
||||||
|
if grep -q '^APP_SECRET=$' "$ROOT_DIR/.env" 2>/dev/null; then
|
||||||
|
echo "Generating APP_SECRET in .env.local..."
|
||||||
|
mkdir -p "$ROOT_DIR"
|
||||||
|
SECRET=$(openssl rand -hex 16)
|
||||||
|
# Write to .env.local so we don't commit it
|
||||||
|
if [ ! -f "$ROOT_DIR/.env.local" ]; then
|
||||||
|
printf "APP_SECRET=%s\n" "$SECRET" > "$ROOT_DIR/.env.local"
|
||||||
|
elif ! grep -q '^APP_SECRET=' "$ROOT_DIR/.env.local"; then
|
||||||
|
printf "APP_SECRET=%s\n" "$SECRET" >> "$ROOT_DIR/.env.local"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare DB
|
||||||
|
pexec php bin/console doctrine:database:create --if-not-exists || true
|
||||||
|
pexec php bin/console doctrine:migrations:migrate -n || true
|
||||||
|
|
||||||
|
# Import JS deps (Importmap/Asset Mapper)
|
||||||
|
pexec php bin/console importmap:install || true
|
||||||
|
|
||||||
|
APP_URL=http://localhost:8080
|
||||||
|
MAILPIT_URL=http://localhost:8025
|
||||||
|
|
||||||
|
cat <<EOT
|
||||||
|
|
||||||
|
Setup complete!
|
||||||
|
|
||||||
|
Open the app: $APP_URL
|
||||||
|
Mailpit (dev): $MAILPIT_URL
|
||||||
|
|
||||||
|
Common commands:
|
||||||
|
(cd docker && $DOCKER_COMPOSE logs -f nginx)
|
||||||
|
(cd docker && $DOCKER_COMPOSE logs -f php)
|
||||||
|
(cd docker && $DOCKER_COMPOSE exec php bash)
|
||||||
|
(cd docker && $DOCKER_COMPOSE down)
|
||||||
|
|
||||||
|
You can re-run this script any time. Use --no-build to skip rebuilding images.
|
||||||
|
EOT
|
||||||
8205
package-lock.json
generated
Normal file
8205
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "escapepage",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "EscapePage Symfony app assets built with Webpack Encore",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "encore dev",
|
||||||
|
"watch": "encore dev --watch",
|
||||||
|
"build": "encore production"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.25.0",
|
||||||
|
"@babel/preset-env": "^7.25.0",
|
||||||
|
"@symfony/webpack-encore": "^4.6.1",
|
||||||
|
"babel-loader": "^9.1.3",
|
||||||
|
"core-js": "^3.37.1",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
|
"regenerator-runtime": "^0.14.1",
|
||||||
|
"webpack": "^5.95.0",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-notifier": "^1.15.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/Command/TestEmailCommand.php
Normal file
55
src/Command/TestEmailCommand.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\Mime\Email;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:mail:test',
|
||||||
|
description: 'Sends a simple test email using the configured mail transport (SendGrid in prod).'
|
||||||
|
)]
|
||||||
|
final class TestEmailCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(private readonly MailerInterface $mailer)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('to', InputArgument::REQUIRED, 'Recipient email address')
|
||||||
|
->addOption('subject', null, InputOption::VALUE_REQUIRED, 'Email subject', 'EscapePage mailer test')
|
||||||
|
->addOption('from', null, InputOption::VALUE_REQUIRED, 'Sender email address (defaults to MAILER_FROM if set)')
|
||||||
|
->addOption('from-name', null, InputOption::VALUE_REQUIRED, 'Sender name', 'EscapePage');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$to = (string) $input->getArgument('to');
|
||||||
|
$subject = (string) $input->getOption('subject');
|
||||||
|
$fromEmail = (string) ($input->getOption('from') ?? ($_ENV['MAILER_FROM'] ?? $_SERVER['MAILER_FROM'] ?? 'no-reply@example.com'));
|
||||||
|
$fromName = (string) $input->getOption('from-name');
|
||||||
|
|
||||||
|
$email = (new Email())
|
||||||
|
->from(new Address($fromEmail, $fromName))
|
||||||
|
->to($to)
|
||||||
|
->subject($subject)
|
||||||
|
->html('<p>This is a test email sent at ' . date('c') . '.</p><p>If you see this, your mailer setup works.</p>')
|
||||||
|
->text('This is a test email sent at ' . date('c') . ". If you see this, your mailer setup works.");
|
||||||
|
|
||||||
|
$this->mailer->send($email);
|
||||||
|
|
||||||
|
$output->writeln('<info>Test email sent to ' . $to . '.</info>');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/Controller/.gitignore
vendored
0
src/Controller/.gitignore
vendored
0
src/Entity/.gitignore
vendored
0
src/Entity/.gitignore
vendored
17
src/Game/Controller/HubController.php
Normal file
17
src/Game/Controller/HubController.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Game\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
final class HubController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route(path: '', name: 'game_hub')]
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
return $this->render('game/hub/index.html.twig');
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/Repository/.gitignore
vendored
0
src/Repository/.gitignore
vendored
18
src/Website/Controller/HomeController.php
Normal file
18
src/Website/Controller/HomeController.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Website\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
final class HomeController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route(path: '', name: 'website_home')]
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
return $this->render(
|
||||||
|
'website/home/index.html.twig');
|
||||||
|
}
|
||||||
|
}
|
||||||
25
symfony.lock
25
symfony.lock
@@ -229,6 +229,15 @@
|
|||||||
"config/routes/security.yaml"
|
"config/routes/security.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/sendgrid-mailer": {
|
||||||
|
"version": "7.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "4.4",
|
||||||
|
"ref": "224aedffb66812dc2b0965dabc14d5f800941da6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"symfony/stimulus-bundle": {
|
"symfony/stimulus-bundle": {
|
||||||
"version": "2.30",
|
"version": "2.30",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@@ -319,6 +328,22 @@
|
|||||||
"config/packages/messenger.yaml"
|
"config/packages/messenger.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/webpack-encore-bundle": {
|
||||||
|
"version": "2.4",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.0",
|
||||||
|
"ref": "719f6110345acb6495e496601fc1b4977d7102b3"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./assets/app.js",
|
||||||
|
"./assets/styles/app.css",
|
||||||
|
"./config/packages/webpack_encore.yaml",
|
||||||
|
"./package.json",
|
||||||
|
"./webpack.config.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
"twig/extra-bundle": {
|
"twig/extra-bundle": {
|
||||||
"version": "v3.21.0"
|
"version": "v3.21.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,17 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}{{ 'site.name'|trans }}{% endblock %}</title>
|
<title>{% block title %}{{ 'site.name'|trans }}{% endblock %}</title>
|
||||||
{% block stylesheets %}{% endblock %}
|
{% block stylesheets %}
|
||||||
|
{{ encore_entry_link_tags('app') }}
|
||||||
|
{{ encore_entry_link_tags('app') }}
|
||||||
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<nav>
|
<nav>
|
||||||
{% set pathinfo = app.request.pathinfo %}
|
{% set pathinfo = app.request.pathinfo %}
|
||||||
{% set is_nl = pathinfo starts with '/nl' %}
|
<a href="/">{{ 'nav.home'|trans }}</a> |
|
||||||
<a href="{{ is_nl ? '/nl' : '/' }}">{{ 'nav.home'|trans }}</a> |
|
<a href="/game">{{ 'nav.game'|trans }}</a>
|
||||||
<a href="{{ is_nl ? '/nl/game' : '/game' }}">{{ 'nav.game'|trans }}</a>
|
|
||||||
<span style="margin-left:1rem">
|
|
||||||
{# Language switcher: URL prefix strategy. Our routes have localized prefixes: en (no prefix), nl (/nl). #}
|
|
||||||
{% set pathinfo = app.request.pathinfo %}
|
|
||||||
{% set is_nl = pathinfo starts with '/nl' %}
|
|
||||||
{% set en_url = is_nl ? pathinfo|slice(3) : pathinfo %}
|
|
||||||
{% set nl_url = is_nl ? pathinfo : '/nl' ~ pathinfo %}
|
|
||||||
<a href="{{ en_url ?: '/' }}">EN</a> /
|
|
||||||
<a href="{{ nl_url }}">NL</a>
|
|
||||||
</span>
|
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
@@ -30,6 +23,8 @@
|
|||||||
<footer>
|
<footer>
|
||||||
<small>© {{ "now"|date("Y") }} {{ 'site.name'|trans }}</small>
|
<small>© {{ "now"|date("Y") }} {{ 'site.name'|trans }}</small>
|
||||||
</footer>
|
</footer>
|
||||||
{% block javascripts %}{% endblock %}
|
{% block javascripts %}
|
||||||
|
{{ encore_entry_script_tags('app') }}
|
||||||
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
22
templates/game/hub/index.html.twig
Normal file
22
templates/game/hub/index.html.twig
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'game.title'|trans({'%site%': ('site.name'|trans)}) }}{% endblock %}
|
||||||
|
|
||||||
|
{# Include Game1-specific CSS in addition to the base app assets #}
|
||||||
|
{% block stylesheets %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('game1') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{ 'game.h1'|trans }}</h1>
|
||||||
|
<p>{{ 'game.description'|trans }}</p>
|
||||||
|
<div class="game1-banner">Game 1 assets are active. Enjoy the challenge!</div>
|
||||||
|
<p><a href="{{ path('website_home') }}">{{ 'link.back_to_website'|trans }}</a></p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{# Include Game1-specific JS in addition to the base app assets #}
|
||||||
|
{% block javascripts %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_script_tags('game1') }}
|
||||||
|
{% endblock %}
|
||||||
9
templates/website/home/index.html.twig
Normal file
9
templates/website/home/index.html.twig
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'home.title'|trans({'%site%': ('site.name'|trans)}) }}{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{ 'home.h1'|trans({'%site%': ('site.name'|trans)}) }}</h1>
|
||||||
|
<p>{{ 'home.description'|trans }}</p>
|
||||||
|
<p><a href="{{ path('game_hub') }}">{{ 'link.enter_game'|trans }}</a></p>
|
||||||
|
{% endblock %}
|
||||||
16
translations/messages.en.yaml
Normal file
16
translations/messages.en.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# General
|
||||||
|
site.name: EscapePage
|
||||||
|
nav.home: Home
|
||||||
|
nav.game: Game
|
||||||
|
link.enter_game: Enter the Game Area
|
||||||
|
link.back_to_website: Back to Website
|
||||||
|
|
||||||
|
# Home page
|
||||||
|
home.title: "Welcome | EscapePage"
|
||||||
|
home.h1: "Welcome to EscapePage"
|
||||||
|
home.description: "This is the public website. Minimal interaction, information about the game, and links."
|
||||||
|
|
||||||
|
# Game hub
|
||||||
|
game.title: "Game Hub | EscapePage"
|
||||||
|
game.h1: "Game Area"
|
||||||
|
game.description: "This is the game hub. Interactive components will be built here."
|
||||||
16
translations/messages.nl.yaml
Normal file
16
translations/messages.nl.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Algemeen
|
||||||
|
site.name: EscapePage
|
||||||
|
nav.home: Start
|
||||||
|
nav.game: Spel
|
||||||
|
link.enter_game: Ga naar het Speelgedeelte
|
||||||
|
link.back_to_website: Terug naar Website
|
||||||
|
|
||||||
|
# Home pagina
|
||||||
|
home.title: "Welkom | %{site}%"
|
||||||
|
home.h1: "Welkom bij %{site}%"
|
||||||
|
home.description: "Dit is de publieke website. Beperkte interactie, informatie over het spel en links."
|
||||||
|
|
||||||
|
# Spel hub
|
||||||
|
game.title: "Spel Hub | %{site}%"
|
||||||
|
game.h1: "Speelgedeelte"
|
||||||
|
game.description: "Dit is de spelhub. Interactieve componenten worden hier later gebouwd."
|
||||||
34
webpack.config.js
Normal file
34
webpack.config.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const Encore = require('@symfony/webpack-encore');
|
||||||
|
|
||||||
|
if (!Encore.isRuntimeEnvironmentConfigured()) {
|
||||||
|
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
|
||||||
|
}
|
||||||
|
|
||||||
|
Encore
|
||||||
|
// where compiled assets will be stored
|
||||||
|
.setOutputPath('public/build/')
|
||||||
|
// public path used by the web server to access the output path
|
||||||
|
.setPublicPath('/build')
|
||||||
|
// clean output before build
|
||||||
|
.cleanupOutputBeforeBuild()
|
||||||
|
// main entry for your app
|
||||||
|
.addEntry('app', './assets/app.js')
|
||||||
|
.addEntry('game1', './assets/game1.js')
|
||||||
|
// split entry chunks for better caching
|
||||||
|
.splitEntryChunks()
|
||||||
|
// will require an extra script tag for runtime.js
|
||||||
|
.enableSingleRuntimeChunk()
|
||||||
|
// features
|
||||||
|
.enableSourceMaps(!Encore.isProduction())
|
||||||
|
.enableVersioning(Encore.isProduction())
|
||||||
|
// Babel config for wide browser support
|
||||||
|
.configureBabelPresetEnv((options) => {
|
||||||
|
options.useBuiltIns = 'usage';
|
||||||
|
options.corejs = 3;
|
||||||
|
options.bugfixes = true;
|
||||||
|
})
|
||||||
|
.autoProvidejQuery(false)
|
||||||
|
.enableBuildNotifications()
|
||||||
|
;
|
||||||
|
|
||||||
|
module.exports = Encore.getWebpackConfig();
|
||||||
Reference in New Issue
Block a user