Files
Escapepage/src/Website/Repository/EscapeRoomReviewRepository.php
T
FrankandClaude Sonnet 5 008887ab60 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>
2026-08-31 00:24:03 +02:00

81 lines
2.2 KiB
PHP

<?php
namespace App\Website\Repository;
use App\Website\Entity\EscapeRoom;
use App\Website\Entity\EscapeRoomReview;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<EscapeRoomReview>
*/
class EscapeRoomReviewRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, EscapeRoomReview::class);
}
public function save(EscapeRoomReview $review, bool $flush = true): void
{
$this->getEntityManager()->persist($review);
if ($flush) {
$this->getEntityManager()->flush();
}
}
/**
* @return EscapeRoomReview[]
*/
public function findVisibleForRoom(EscapeRoom $room): array
{
return $this->createQueryBuilder('rev')
->where('rev.escapeRoom = :room')
->andWhere('rev.deletedAt IS NULL')
->setParameter('room', $room)
->orderBy('rev.createdAt', 'DESC')
->getQuery()
->getResult();
}
/**
* @return array{avg: ?float, count: int}
*/
public function ratingSummary(EscapeRoom $room): array
{
$row = $this->createQueryBuilder('rev')
->select('AVG(rev.rating) AS avg, COUNT(rev.id) AS count')
->where('rev.escapeRoom = :room')
->andWhere('rev.deletedAt IS NULL')
->setParameter('room', $room)
->getQuery()
->getSingleResult();
$count = (int) $row['count'];
return [
'avg' => $count > 0 ? round((float) $row['avg'], 1) : null,
'count' => $count,
];
}
/**
* Recent reviews across all rooms, for admin moderation.
*
* @return EscapeRoomReview[]
*/
public function findRecent(int $limit = 200): array
{
return $this->createQueryBuilder('rev')
->addSelect('room')
->join('rev.escapeRoom', 'room')
->where('rev.deletedAt IS NULL')
->orderBy('rev.createdAt', 'DESC')
->setMaxResults($limit)
->getQuery()
->getResult();
}
}