Add physical escape room map, reviews, and website admin

Public:
- /escape-rooms: Leaflet + OpenStreetMap map with marker clustering and a
  searchable list, both built from a cached slim JSON feed
- /escape-rooms/{id}-{slug}: room detail with a mini-map and user reviews
  (rating + title + body, login required)
- /escape-rooms/suggest: visitor submission form with click-to-drop-pin map
  and reCAPTCHA v3; new rooms land as pending

Data:
- EscapeRoom / EscapeRoomReview entities (soft-delete), migration
- app:escaperooms:import-osm console command pulls leisure=escape_game from
  the Overpass API, upserts by (osmType, osmId), skips locked rows;
  --tiled / --area / --bbox options

Admin (new "Website Admin" area, separate from Game Admin):
- src/Website/Controller/AdminEscapeRoomController.php at /admin/escape-rooms:
  status-filtered list, new/edit, publish/hide/reject, delete, review removal
- game admin sidebar extracted into a block and cross-linked

Assets: leaflet + leaflet.markercluster; escaperoom-map and escaperoom-detail
Encore entries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Frank
2026-08-31 00:24:03 +02:00
co-authored by Claude Sonnet 5
parent 0edaa9d8cc
commit 008887ab60
29 changed files with 2532 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260829150000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add escape_room and escape_room_review tables for the physical escape room map and reviews';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE escape_room (
id INT AUTO_INCREMENT NOT NULL,
submitted_by_id INT DEFAULT NULL,
name VARCHAR(255) NOT NULL,
venue VARCHAR(255) DEFAULT NULL,
street VARCHAR(255) DEFAULT NULL,
house_number VARCHAR(32) DEFAULT NULL,
postcode VARCHAR(32) DEFAULT NULL,
city VARCHAR(128) DEFAULT NULL,
country VARCHAR(2) DEFAULT NULL,
latitude NUMERIC(10, 7) NOT NULL,
longitude NUMERIC(10, 7) NOT NULL,
website VARCHAR(511) DEFAULT NULL,
phone VARCHAR(64) DEFAULT NULL,
source VARCHAR(16) NOT NULL,
osm_type VARCHAR(8) DEFAULT NULL,
osm_id BIGINT DEFAULT NULL,
status VARCHAR(16) NOT NULL,
locked TINYINT(1) DEFAULT 0 NOT NULL,
created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)',
updated_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)',
deleted_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)',
INDEX idx_escape_room_submitted_by (submitted_by_id),
UNIQUE INDEX uniq_escape_room_osm (osm_type, osm_id),
INDEX idx_escape_room_status (status),
INDEX idx_escape_room_bbox (latitude, longitude),
PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE escape_room_review (
id INT AUTO_INCREMENT NOT NULL,
escape_room_id INT NOT NULL,
author_id INT DEFAULT NULL,
rating SMALLINT NOT NULL,
title VARCHAR(150) NOT NULL,
body LONGTEXT NOT NULL,
created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)',
deleted_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)',
INDEX idx_escape_room_review_room (escape_room_id),
INDEX idx_escape_room_review_author (author_id),
PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
SQL);
$this->addSql('ALTER TABLE escape_room ADD CONSTRAINT fk_escape_room_submitted_by FOREIGN KEY (submitted_by_id) REFERENCES `user` (id) ON DELETE SET NULL');
$this->addSql('ALTER TABLE escape_room_review ADD CONSTRAINT fk_escape_room_review_room FOREIGN KEY (escape_room_id) REFERENCES escape_room (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE escape_room_review ADD CONSTRAINT fk_escape_room_review_author FOREIGN KEY (author_id) REFERENCES `user` (id) ON DELETE SET NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE escape_room_review DROP FOREIGN KEY fk_escape_room_review_room');
$this->addSql('ALTER TABLE escape_room_review DROP FOREIGN KEY fk_escape_room_review_author');
$this->addSql('ALTER TABLE escape_room DROP FOREIGN KEY fk_escape_room_submitted_by');
$this->addSql('DROP TABLE escape_room_review');
$this->addSql('DROP TABLE escape_room');
}
}