Private
Public Access
1
0

chg: pkg: upgrade the doctrine related back-end pkgs to the latest available version #7

This commit is contained in:
2026-04-20 09:05:36 +02:00
parent 5f856e4d70
commit 175581cdd5
30 changed files with 456 additions and 1015 deletions

View File

@@ -174,7 +174,6 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc, isEnvDev = false
<Dialog <Dialog
open={open} open={open}
onClose={0 < waitingCountdown ? undefined : onClose} onClose={0 < waitingCountdown ? undefined : onClose}
disableEscapeKeyDown={0 < waitingCountdown}
sx={DIALOG_SX} sx={DIALOG_SX}
> >
<div className="opd"> <div className="opd">

View File

@@ -15,8 +15,8 @@
"ext-iconv": "*", "ext-iconv": "*",
"ext-json": "*", "ext-json": "*",
"doctrine/dbal": "^4.3", "doctrine/dbal": "^4.3",
"doctrine/doctrine-bundle": "^2.14", "doctrine/doctrine-bundle": "^3.2",
"doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/doctrine-migrations-bundle": "^4.0",
"doctrine/orm": "^3.5", "doctrine/orm": "^3.5",
"endroid/qr-code": "^6.1", "endroid/qr-code": "^6.1",
"firebase/php-jwt": "^7.0", "firebase/php-jwt": "^7.0",

123
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "af396dcff9af321b624ed8fdbe437cea", "content-hash": "6f52982c6e7461757ab66e20a764b9a8",
"packages": [ "packages": [
{ {
"name": "aws/aws-crt-php", "name": "aws/aws-crt-php",
@@ -563,63 +563,57 @@
}, },
{ {
"name": "doctrine/doctrine-bundle", "name": "doctrine/doctrine-bundle",
"version": "2.18.2", "version": "3.2.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/DoctrineBundle.git", "url": "https://github.com/doctrine/DoctrineBundle.git",
"reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546" "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546", "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/af84173db6978c3d2688ea3bcf3a91720b0704ce",
"reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546", "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/dbal": "^3.7.0 || ^4.0", "doctrine/dbal": "^4.0",
"doctrine/deprecations": "^1.0", "doctrine/deprecations": "^1.0",
"doctrine/persistence": "^3.1 || ^4", "doctrine/persistence": "^4",
"doctrine/sql-formatter": "^1.0.1", "doctrine/sql-formatter": "^1.0.1",
"php": "^8.1", "php": "^8.4",
"symfony/cache": "^6.4 || ^7.0", "symfony/cache": "^6.4 || ^7.0 || ^8.0",
"symfony/config": "^6.4 || ^7.0", "symfony/config": "^6.4 || ^7.0 || ^8.0",
"symfony/console": "^6.4 || ^7.0", "symfony/console": "^6.4 || ^7.0 || ^8.0",
"symfony/dependency-injection": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0",
"symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3 || ^8.0",
"symfony/framework-bundle": "^6.4 || ^7.0", "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0",
"symfony/service-contracts": "^2.5 || ^3" "symfony/service-contracts": "^3"
}, },
"conflict": { "conflict": {
"doctrine/annotations": ">=3.0", "doctrine/orm": "<3.0 || >=4.0",
"doctrine/cache": "< 1.11", "twig/twig": "<3.0.4"
"doctrine/orm": "<2.17 || >=4.0",
"symfony/var-exporter": "< 6.4.1 || 7.0.0",
"twig/twig": "<2.13 || >=3.0 <3.0.4"
}, },
"require-dev": { "require-dev": {
"doctrine/annotations": "^1 || ^2",
"doctrine/cache": "^1.11 || ^2.0",
"doctrine/coding-standard": "^14", "doctrine/coding-standard": "^14",
"doctrine/orm": "^2.17 || ^3.1", "doctrine/orm": "^3.4.4",
"friendsofphp/proxy-manager-lts": "^1.0",
"phpstan/phpstan": "2.1.1", "phpstan/phpstan": "2.1.1",
"phpstan/phpstan-phpunit": "2.0.3", "phpstan/phpstan-phpunit": "2.0.3",
"phpstan/phpstan-strict-rules": "^2", "phpstan/phpstan-strict-rules": "^2",
"phpunit/phpunit": "^10.5.53 || ^12.3.10", "phpstan/phpstan-symfony": "^2.0",
"psr/log": "^1.1.4 || ^2.0 || ^3.0", "phpunit/phpunit": "^12.3.10",
"symfony/doctrine-messenger": "^6.4 || ^7.0", "psr/log": "^3.0",
"symfony/expression-language": "^6.4 || ^7.0", "symfony/doctrine-messenger": "^6.4 || ^7.0 || ^8.0",
"symfony/messenger": "^6.4 || ^7.0", "symfony/expression-language": "^6.4 || ^7.0 || ^8.0",
"symfony/property-info": "^6.4 || ^7.0", "symfony/messenger": "^6.4 || ^7.0 || ^8.0",
"symfony/security-bundle": "^6.4 || ^7.0", "symfony/property-info": "^6.4 || ^7.0 || ^8.0",
"symfony/stopwatch": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0",
"symfony/string": "^6.4 || ^7.0", "symfony/stopwatch": "^6.4 || ^7.0 || ^8.0",
"symfony/twig-bridge": "^6.4 || ^7.0", "symfony/string": "^6.4 || ^7.0 || ^8.0",
"symfony/validator": "^6.4 || ^7.0", "symfony/twig-bridge": "^6.4 || ^7.0 || ^8.0",
"symfony/var-exporter": "^6.4.1 || ^7.0.1", "symfony/validator": "^6.4 || ^7.0 || ^8.0",
"symfony/web-profiler-bundle": "^6.4 || ^7.0", "symfony/web-profiler-bundle": "^6.4 || ^7.0 || ^8.0",
"symfony/yaml": "^6.4 || ^7.0", "symfony/yaml": "^6.4 || ^7.0 || ^8.0",
"twig/twig": "^2.14.7 || ^3.0.4" "twig/twig": "^3.21.1"
}, },
"suggest": { "suggest": {
"doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.",
@@ -664,7 +658,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/DoctrineBundle/issues", "issues": "https://github.com/doctrine/DoctrineBundle/issues",
"source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2" "source": "https://github.com/doctrine/DoctrineBundle/tree/3.2.2"
}, },
"funding": [ "funding": [
{ {
@@ -680,41 +674,48 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-12-20T21:35:32+00:00" "time": "2025-12-24T12:24:29+00:00"
}, },
{ {
"name": "doctrine/doctrine-migrations-bundle", "name": "doctrine/doctrine-migrations-bundle",
"version": "3.7.0", "version": "4.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git",
"reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b" "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/1e380c6dd8ac8488217f39cff6b77e367f1a644b", "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/20505da78735744fb4a42a3bb9a416b345ad6f7c",
"reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b", "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/doctrine-bundle": "^2.4 || ^3.0", "doctrine/dbal": "^4",
"doctrine/doctrine-bundle": "^3",
"doctrine/migrations": "^3.2", "doctrine/migrations": "^3.2",
"php": "^7.2 || ^8.0", "php": "^8.4",
"symfony/deprecation-contracts": "^2.1 || ^3", "psr/log": "^3",
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" "symfony/config": "^6.4 || ^7.0 || ^8.0",
"symfony/console": "^6.4 || ^7.0 || ^8.0",
"symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0",
"symfony/deprecation-contracts": "^3",
"symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0",
"symfony/http-foundation": "^6.4 || ^7.0 || ^8.0",
"symfony/http-kernel": "^6.4 || ^7.0 || ^8.0",
"symfony/service-contracts": "^3.0"
}, },
"require-dev": { "require-dev": {
"composer/semver": "^3.0", "composer/semver": "^3.0",
"doctrine/coding-standard": "^12 || ^14", "doctrine/coding-standard": "^14",
"doctrine/orm": "^2.6 || ^3", "doctrine/orm": "^3",
"phpstan/phpstan": "^1.4 || ^2", "phpstan/phpstan": "^2",
"phpstan/phpstan-deprecation-rules": "^1 || ^2", "phpstan/phpstan-deprecation-rules": "^2",
"phpstan/phpstan-phpunit": "^1 || ^2", "phpstan/phpstan-phpunit": "^2",
"phpstan/phpstan-strict-rules": "^1.1 || ^2", "phpstan/phpstan-strict-rules": "^2",
"phpstan/phpstan-symfony": "^1.3 || ^2", "phpstan/phpstan-symfony": "^2",
"phpunit/phpunit": "^8.5 || ^9.5", "phpunit/phpunit": "^12.5",
"symfony/phpunit-bridge": "^6.3 || ^7 || ^8", "symfony/var-exporter": "^6.4 || ^7 || ^8"
"symfony/var-exporter": "^5.4 || ^6 || ^7 || ^8"
}, },
"type": "symfony-bundle", "type": "symfony-bundle",
"autoload": { "autoload": {
@@ -749,7 +750,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues",
"source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.7.0" "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/4.0.0"
}, },
"funding": [ "funding": [
{ {
@@ -765,7 +766,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-11-15T19:02:59+00:00" "time": "2025-12-05T08:14:38+00:00"
}, },
{ {
"name": "doctrine/event-manager", "name": "doctrine/event-manager",

View File

@@ -11,7 +11,7 @@ doctrine:
url: '%env(resolve:DATABASE_URL)%' url: '%env(resolve:DATABASE_URL)%'
orm: orm:
auto_generate_proxy_classes: '%kernel.debug%' enable_native_lazy_objects: true
naming_strategy: doctrine.orm.naming_strategy.underscore naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true auto_mapping: true
mappings: mappings:

View File

@@ -91,7 +91,7 @@ class GameController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$contactMessage->setIpAddress($request->getClientIp()); $contactMessage->ipAddress = $request->getClientIp();
$em->persist($contactMessage); $em->persist($contactMessage);
$em->flush(); $em->flush();

View File

@@ -137,16 +137,16 @@ class MercureController extends AbstractController
$result = array_map(static function (PlayedGame $g): array { $result = array_map(static function (PlayedGame $g): array {
$name = match (true) { $name = match (true) {
null !== $g->getRed() => $g->getRed()->getUsername(), null !== $g->red => $g->red->getUsername(),
null !== $g->getRedAnon() => $g->getRedAnon()->getUserName(), null !== $g->redAnon => $g->redAnon->userName,
null !== $g->getBlue() => $g->getBlue()->getUsername(), null !== $g->blue => $g->blue->getUsername(),
default => $g->getBlueAnon()?->getUserName() ?? 'Unknown', default => $g->blueAnon?->userName ?? 'Unknown',
}; };
return [ return [
'gameAssoc' => $g->getGameAssoc(), 'gameAssoc' => $g->gameAssoc,
'name' => $name, 'name' => $name,
'since' => $g->getCreated()?->format(\DateTimeInterface::ATOM) ?? '', 'since' => $g->created?->format(\DateTimeInterface::ATOM) ?? '',
]; ];
}, $games); }, $games);

View File

@@ -78,22 +78,22 @@ class ProfileController extends AbstractController
$since = new DateTime('first day of -5 months midnight'); $since = new DateTime('first day of -5 months midnight');
$recentGames = $this->repo->findFinishedForUserSince($user, $since); $recentGames = $this->repo->findFinishedForUserSince($user, $since);
$userId = $user->getId(); $userId = $user->id;
foreach ($recentGames as $game) { foreach ($recentGames as $game) {
if (!$game->getUpdated()) { if (!$game->updated) {
continue; continue;
} }
$month = $game->getUpdated()->format('Y-m'); $month = $game->updated->format('Y-m');
if (!isset($monthlyData[$month])) { if (!isset($monthlyData[$month])) {
continue; continue;
} }
$isRed = $game->getRed()?->getId() === $userId; $isRed = $game->red?->id === $userId;
$myPts = $isRed ? $game->getRedPoints() : $game->getBluePoints(); $myPts = $isRed ? $game->redPoints : $game->bluePoints;
$oppPts = $isRed ? $game->getBluePoints() : $game->getRedPoints(); $oppPts = $isRed ? $game->bluePoints : $game->redPoints;
$resign = $game->getResign(); $resign = $game->resign;
$myColor = $isRed ? 'red' : 'blue'; $myColor = $isRed ? 'red' : 'blue';
$oppColor = $isRed ? 'blue' : 'red'; $oppColor = $isRed ? 'blue' : 'red';
@@ -131,12 +131,12 @@ class ProfileController extends AbstractController
], ],
'recent' => ($recent = $this->repo->findRecentFinishedForUser($user, 30)), 'recent' => ($recent = $this->repo->findRecentFinishedForUser($user, 30)),
'gamesData' => array_map(function (PlayedGame $game) use ($userId, $cacheManager): array { 'gamesData' => array_map(function (PlayedGame $game) use ($userId, $cacheManager): array {
$isRed = $game->getRed()?->getId() === $userId; $isRed = $game->red?->id === $userId;
$resign = $game->getResign(); $resign = $game->resign;
$myColor = $isRed ? 'red' : 'blue'; $myColor = $isRed ? 'red' : 'blue';
$oppColor = $isRed ? 'blue' : 'red'; $oppColor = $isRed ? 'blue' : 'red';
$myPts = $isRed ? $game->getRedPoints() : $game->getBluePoints(); $myPts = $isRed ? $game->redPoints : $game->bluePoints;
$oppPts = $isRed ? $game->getBluePoints() : $game->getRedPoints(); $oppPts = $isRed ? $game->bluePoints : $game->redPoints;
$result = 'draw'; $result = 'draw';
if ($resign === $myColor) $result = 'loss'; if ($resign === $myColor) $result = 'loss';
@@ -146,33 +146,33 @@ class ProfileController extends AbstractController
elseif ($myPts < $oppPts) $result = 'loss'; elseif ($myPts < $oppPts) $result = 'loss';
} }
$redAvatarPath = $game->getRed()?->getAvatarPath(); $redAvatarPath = $game->red?->avatarPath;
$blueAvatarPath = $game->getBlue()?->getAvatarPath(); $blueAvatarPath = $game->blue?->avatarPath;
return [ return [
'id' => $game->getId(), 'id' => $game->id,
'uuid' => $game->getUuid()?->toRfc4122(), 'uuid' => $game->uuid?->toRfc4122(),
'redName' => 'redName' =>
$game->getRed()?->getUsername() ?? $game->getRedAnon()?->getUserName() ?? 'Guest', $game->red?->getUsername() ?? $game->redAnon?->userName ?? 'Guest',
'blueName' => 'blueName' =>
$game->getBlue()?->getUsername() ?? $game->getBlueAnon()?->getUserName() ?? 'Guest', $game->blue?->getUsername() ?? $game->blueAnon?->userName ?? 'Guest',
'redAvatar' => $redAvatarPath ? $cacheManager->generateUrl($redAvatarPath, 'avatar_thumb') : null, 'redAvatar' => $redAvatarPath ? $cacheManager->generateUrl($redAvatarPath, 'avatar_thumb') : null,
'blueAvatar' => $blueAvatarPath ? $cacheManager->generateUrl($blueAvatarPath, 'avatar_thumb') : null, 'blueAvatar' => $blueAvatarPath ? $cacheManager->generateUrl($blueAvatarPath, 'avatar_thumb') : null,
'redPoints' => $game->getRedPoints(), 'redPoints' => $game->redPoints,
'bluePoints' => $game->getBluePoints(), 'bluePoints' => $game->bluePoints,
'redExplodedBomb' => $game->getRedExplodedBomb(), 'redExplodedBomb' => $game->redExplodedBomb,
'blueExplodedBomb' => $game->getBlueExplodedBomb(), 'blueExplodedBomb' => $game->blueExplodedBomb,
'resign' => $resign, 'resign' => $resign,
'created' => $game->getCreated()?->format('Y-m-d H:i'), 'created' => $game->created?->format('Y-m-d H:i'),
'date' => $game->getUpdated()?->format('Y-m-d H:i'), 'date' => $game->updated?->format('Y-m-d H:i'),
'isRed' => $isRed, 'isRed' => $isRed,
'result' => $result, 'result' => $result,
'myPoints' => $myPts, 'myPoints' => $myPts,
'oppPoints' => $oppPts, 'oppPoints' => $oppPts,
'redBonusPoints' => $game->getRedBonusPoints() ?? 0, 'redBonusPoints' => $game->redBonusPoints ?? 0,
'blueBonusPoints' => $game->getBlueBonusPoints() ?? 0, 'blueBonusPoints' => $game->blueBonusPoints ?? 0,
'redBonusStats' => $game->getRedBonusStats() ?? [], 'redBonusStats' => $game->redBonusStats ?? [],
'blueBonusStats' => $game->getBlueBonusStats() ?? [], 'blueBonusStats' => $game->blueBonusStats ?? [],
]; ];
}, $recent), }, $recent),
'chartData' => [ 'chartData' => [
@@ -202,10 +202,10 @@ class ProfileController extends AbstractController
$mines = []; $mines = [];
$bonus = []; $bonus = [];
foreach ($recent as $i => $game) { foreach ($recent as $i => $game) {
$isRed = $game->getRed()?->getId() === $userId; $isRed = $game->red?->id === $userId;
$labels[] = '#' . ($i + 1); $labels[] = '#' . ($i + 1);
$mines[] = (int)($isRed ? $game->getRedPoints() : $game->getBluePoints()); $mines[] = (int)($isRed ? $game->redPoints : $game->bluePoints);
$bonus[] = (float)($isRed ? $game->getRedBonusPoints() : $game->getBlueBonusPoints()) ?: 0; $bonus[] = (float)($isRed ? $game->redBonusPoints : $game->blueBonusPoints) ?: 0;
} }
return ['labels' => $labels, 'mines' => $mines, 'bonus' => $bonus]; return ['labels' => $labels, 'mines' => $mines, 'bonus' => $bonus];
@@ -224,17 +224,17 @@ class ProfileController extends AbstractController
throw $this->createNotFoundException('Battle not found.'); throw $this->createNotFoundException('Battle not found.');
} }
$redName = $game->getRed()?->getUsername() ?? ($game->getRedAnon() !== null ? 'Anonymous' : 'Guest'); $redName = $game->red?->getUsername() ?? ($game->redAnon !== null ? 'Anonymous' : 'Guest');
$blueName = $game->getBlue()?->getUsername() ?? ($game->getBlueAnon() !== null ? 'Anonymous' : 'Guest'); $blueName = $game->blue?->getUsername() ?? ($game->blueAnon !== null ? 'Anonymous' : 'Guest');
$redPts = $game->getRedPoints(); $redPts = $game->redPoints;
$bluePts = $game->getBluePoints(); $bluePts = $game->bluePoints;
$resign = $game->getResign(); $resign = $game->resign;
$redAvatar = $game->getRed()?->getAvatarPath(); $redAvatar = $game->red?->avatarPath;
$blueAvatar = $game->getBlue()?->getAvatarPath(); $blueAvatar = $game->blue?->avatarPath;
$redBonusPoints = $game->getRedBonusPoints() ?? 0; $redBonusPoints = $game->redBonusPoints ?? 0;
$blueBonusPoints = $game->getBlueBonusPoints() ?? 0; $blueBonusPoints = $game->blueBonusPoints ?? 0;
$redBonusStats = $game->getRedBonusStats() ?? []; $redBonusStats = $game->redBonusStats ?? [];
$blueBonusStats = $game->getBlueBonusStats() ?? []; $blueBonusStats = $game->blueBonusStats ?? [];
if ($resign === 'red') { if ($resign === 'red') {
$summary = "$redName resigned — $blueName wins"; $summary = "$redName resigned — $blueName wins";
@@ -321,7 +321,7 @@ class ProfileController extends AbstractController
$ext = $file->guessExtension() ?? 'jpg'; $ext = $file->guessExtension() ?? 'jpg';
$newPath = sprintf('avatar/%s.%s', Uuid::v4()->toRfc4122(), $ext); $newPath = sprintf('avatar/%s.%s', Uuid::v4()->toRfc4122(), $ext);
$oldPath = $user->getAvatarPath(); $oldPath = $user->avatarPath;
/** Remove old file and any cached thumbnails */ /** Remove old file and any cached thumbnails */
if ($oldPath) { if ($oldPath) {
@@ -343,7 +343,7 @@ class ProfileController extends AbstractController
} }
fclose($stream); fclose($stream);
$user->setAvatarPath($newPath); $user->avatarPath = $newPath;
$em->flush(); $em->flush();
return $this->json([ return $this->json([
@@ -360,18 +360,18 @@ class ProfileController extends AbstractController
$credentials = $this->webAuthnService->getCredentialsForUser($user); $credentials = $this->webAuthnService->getCredentialsForUser($user);
$credentialsData = array_map(fn($cred) => [ $credentialsData = array_map(fn($cred) => [
'id' => $cred->getId(), 'id' => $cred->id,
'credentialName' => $cred->getCredentialName(), 'credentialName' => $cred->credentialName,
'createdAt' => $cred->getCreatedAt()?->format('Y-m-d H:i:s'), 'createdAt' => $cred->createdAt?->format('Y-m-d H:i:s'),
'lastUsedAt' => $cred->getLastUsedAt()?->format('Y-m-d H:i:s'), 'lastUsedAt' => $cred->lastUsedAt?->format('Y-m-d H:i:s'),
'isBackupEligible' => $cred->isBackupEligible(), 'isBackupEligible' => $cred->isBackupEligible,
'isBackupAuthenticated' => $cred->isBackupAuthenticated(), 'isBackupAuthenticated' => $cred->isBackupAuthenticated,
], $credentials); ], $credentials);
return $this->render('Security/profile_security.html.twig', [ return $this->render('Security/profile_security.html.twig', [
'credentials' => $credentialsData, 'credentials' => $credentialsData,
'isTotpEnabled' => $user->isTotpAuthenticationEnabled(), 'isTotpEnabled' => $user->isTotpAuthenticationEnabled(),
'backupCodesCount' => count($user->getBackupCodes()), 'backupCodesCount' => count($user->backupCodes),
]); ]);
} }
} }

View File

@@ -86,10 +86,9 @@ class SecurityController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$token = bin2hex(random_bytes(32)); $token = bin2hex(random_bytes(32));
$user $user->isVerified = false;
->setIsVerified(false) $user->verificationToken = $token;
->setVerificationToken($token) $user->password = $hasher->hashPassword($user, $form->get('plainPassword')->getData());
->setPassword($hasher->hashPassword($user, $form->get('plainPassword')->getData()));
$em->persist($user); $em->persist($user);
$em->flush(); $em->flush();
@@ -108,7 +107,7 @@ class SecurityController extends AbstractController
$mailer->send( $mailer->send(
new TemplatedEmail() new TemplatedEmail()
->from('noreply@mineseeker.hu') ->from('noreply@mineseeker.hu')
->to($user->getEmail()) ->to($user->email)
->subject('Activate your MineSeeker account') ->subject('Activate your MineSeeker account')
->htmlTemplate('emails/activation.html.twig') ->htmlTemplate('emails/activation.html.twig')
->context([ ->context([
@@ -130,7 +129,7 @@ class SecurityController extends AbstractController
]) ])
); );
$this->addFlash('verify_email', $user->getEmail()); $this->addFlash('verify_email', $user->email);
return $this->redirectToRoute('MineSeekerBundle_register'); return $this->redirectToRoute('MineSeekerBundle_register');
} }
@@ -156,11 +155,10 @@ class SecurityController extends AbstractController
$email = $form->get('email')->getData(); $email = $form->get('email')->getData();
$user = $userRepository->findOneByEmail($email); $user = $userRepository->findOneByEmail($email);
if ($user && $user->isVerified()) { if ($user && $user->isVerified) {
$token = bin2hex(random_bytes(32)); $token = bin2hex(random_bytes(32));
$user $user->resetToken = $token;
->setResetToken($token) $user->resetTokenExpiresAt = new DateTime('+1 hour');
->setResetTokenExpiresAt(new DateTime('+1 hour'));
$em->flush(); $em->flush();
$resetUrl = $this->generateUrl( $resetUrl = $this->generateUrl(
@@ -206,7 +204,7 @@ class SecurityController extends AbstractController
): Response { ): Response {
$user = $userRepository->findOneByResetToken($token); $user = $userRepository->findOneByResetToken($token);
if (!$user || $user->getResetTokenExpiresAt() < new DateTime()) { if (!$user || $user->resetTokenExpiresAt < new DateTime()) {
$this->addFlash('error', 'This password reset link is invalid or has expired.'); $this->addFlash('error', 'This password reset link is invalid or has expired.');
return $this->redirectToRoute('MineSeekerBundle_forgot_password'); return $this->redirectToRoute('MineSeekerBundle_forgot_password');
} }
@@ -215,10 +213,9 @@ class SecurityController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$user $user->password = $hasher->hashPassword($user, $form->get('plainPassword')->getData());
->setPassword($hasher->hashPassword($user, $form->get('plainPassword')->getData())) $user->resetToken = null;
->setResetToken(null) $user->resetTokenExpiresAt = null;
->setResetTokenExpiresAt(null);
$em->flush(); $em->flush();
$this->addFlash('success', 'Your password has been reset. You can now sign in.'); $this->addFlash('success', 'Your password has been reset. You can now sign in.');
@@ -239,7 +236,8 @@ class SecurityController extends AbstractController
return $this->redirectToRoute('MineSeekerBundle_login'); return $this->redirectToRoute('MineSeekerBundle_login');
} }
$user->setIsVerified(true)->setVerificationToken(null); $user->isVerified = true;
$user->verificationToken = null;
$em->flush(); $em->flush();
/** Send admin notification about account activation */ /** Send admin notification about account activation */

View File

@@ -156,16 +156,16 @@ class TwoFactorController extends AbstractController
$code = $request->request->getString('_auth_code'); $code = $request->request->getString('_auth_code');
// Temporarily set the pending secret to verify the code // Temporarily set the pending secret to verify the code
$user->setTotpSecret($pendingSecret); $user->totpSecret = $pendingSecret;
if (!$this->totpAuthenticator->checkCode($user, $code)) { if (!$this->totpAuthenticator->checkCode($user, $code)) {
$user->setTotpSecret(null); $user->totpSecret = null;
$this->addFlash('error', 'Invalid verification code. Please try again.'); $this->addFlash('error', 'Invalid verification code. Please try again.');
return $this->redirectToRoute('MineSeekerBundle_2fa_setup'); return $this->redirectToRoute('MineSeekerBundle_2fa_setup');
} }
$backupCodes = $this->generateBackupCodes(); $backupCodes = $this->generateBackupCodes();
$user->setBackupCodes($backupCodes); $user->backupCodes = $backupCodes;
$this->em->flush(); $this->em->flush();
$request->getSession()->remove('totp_pending_secret'); $request->getSession()->remove('totp_pending_secret');
@@ -187,8 +187,8 @@ class TwoFactorController extends AbstractController
/** @var User $user */ /** @var User $user */
$user = $this->getUser(); $user = $this->getUser();
$user->setTotpSecret(null); $user->totpSecret = null;
$user->setBackupCodes([]); $user->backupCodes = [];
$this->em->flush(); $this->em->flush();
$this->addFlash('success', 'Two-factor authentication has been disabled.'); $this->addFlash('success', 'Two-factor authentication has been disabled.');
@@ -217,7 +217,7 @@ class TwoFactorController extends AbstractController
} }
$backupCodes = $this->generateBackupCodes(); $backupCodes = $this->generateBackupCodes();
$user->setBackupCodes($backupCodes); $user->backupCodes = $backupCodes;
$this->em->flush(); $this->em->flush();
$this->addFlash('2fa_backup_codes', $backupCodes); $this->addFlash('2fa_backup_codes', $backupCodes);

View File

@@ -64,7 +64,7 @@ class WebAuthnController extends AbstractController
$userEntity = new PublicKeyCredentialUserEntity( $userEntity = new PublicKeyCredentialUserEntity(
$user->getUserIdentifier(), $user->getUserIdentifier(),
(string)$user->getId(), (string)$user->id,
$user->getUsername(), $user->getUsername(),
); );
@@ -141,7 +141,7 @@ class WebAuthnController extends AbstractController
} }
/** Store the credential with user ID for later retrieval during authentication */ /** Store the credential with user ID for later retrieval during authentication */
$credentialJson['userId'] = $user->getId(); $credentialJson['userId'] = $user->id;
$credentialJson['username'] = $user->getUsername(); $credentialJson['username'] = $user->getUsername();
/** Save the credential data directly */ /** Save the credential data directly */
@@ -173,12 +173,12 @@ class WebAuthnController extends AbstractController
$credentials = $this->webAuthnService->getCredentialsForUser($user); $credentials = $this->webAuthnService->getCredentialsForUser($user);
return new JsonResponse(array_map(fn($credential) => [ return new JsonResponse(array_map(fn($credential) => [
'id' => $credential->getId(), 'id' => $credential->id,
'name' => $credential->getCredentialName(), 'name' => $credential->credentialName,
'createdAt' => $credential->getCreatedAt()?->format('Y-m-d H:i:s'), 'createdAt' => $credential->createdAt?->format('Y-m-d H:i:s'),
'lastUsedAt' => $credential->getLastUsedAt()?->format('Y-m-d H:i:s'), 'lastUsedAt' => $credential->lastUsedAt?->format('Y-m-d H:i:s'),
'isBackupEligible' => $credential->isBackupEligible(), 'isBackupEligible' => $credential->isBackupEligible,
'isBackupAuthenticated' => $credential->isBackupAuthenticated(), 'isBackupAuthenticated' => $credential->isBackupAuthenticated,
], $credentials)); ], $credentials));
} }

View File

@@ -34,95 +34,29 @@ use Doctrine\ORM\Mapping\Table;
class ContactMessage class ContactMessage
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[Column] #[Column]
private string $name; public string $name;
#[Column] #[Column]
private string $email; public string $email;
#[Column(type: Types::TEXT)] #[Column(type: Types::TEXT)]
private string $content; public string $content;
#[Column] #[Column]
private bool $consent = false; public bool $consent = false;
#[Column] #[Column]
private DateTimeImmutable $createdAt; public private(set) DateTimeImmutable $createdAt;
#[Column(length: 45, nullable: true)] #[Column(length: 45, nullable: true)]
private ?string $ipAddress = null; public ?string $ipAddress = null;
public function __construct() public function __construct()
{ {
$this->createdAt = new DateTimeImmutable(); $this->createdAt = new DateTimeImmutable();
} }
public function getId(): ?int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getContent(): string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function isConsent(): bool
{
return $this->consent;
}
public function setConsent(bool $consent): self
{
$this->consent = $consent;
return $this;
}
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
public function getIpAddress(): ?string
{
return $this->ipAddress;
}
public function setIpAddress(?string $ipAddress): self
{
$this->ipAddress = $ipAddress;
return $this;
}
} }

View File

@@ -32,76 +32,20 @@ use Doctrine\ORM\Mapping\Id;
class Gamer class Gamer
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[Column(length: 100)] #[Column(length: 100)]
private ?string $userName = null; public ?string $userName = null;
#[Column(length: 20, nullable: true)] #[Column(length: 20, nullable: true)]
private ?string $ip = null; public ?string $ip = null;
#[Column(length: 100, nullable: true)] #[Column(length: 100, nullable: true)]
private ?string $country = null; public ?string $country = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?string $userAgent = null; public ?string $userAgent = null;
#[Column(type: Types::DATETIME_MUTABLE)] #[Column(type: Types::DATETIME_MUTABLE)]
private ?DateTime $connTimestamp = null; public ?DateTime $connTimestamp = null;
public function getId(): ?int
{
return $this->id;
}
public function getUserName(): ?string
{
return $this->userName;
}
public function setUserName(?string $userName): void
{
$this->userName = $userName;
}
public function getIp(): ?string
{
return $this->ip;
}
public function setIp(?string $ip): void
{
$this->ip = $ip;
}
public function getCountry(): ?string
{
return $this->country;
}
public function setCountry(?string $country): void
{
$this->country = $country;
}
public function getUserAgent(): ?string
{
return $this->userAgent;
}
public function setUserAgent(?string $userAgent): void
{
$this->userAgent = $userAgent;
}
public function getConnTimestamp(): ?DateTime
{
return $this->connTimestamp;
}
public function setConnTimestamp(?DateTime $connTimestamp): void
{
$this->connTimestamp = $connTimestamp;
}
} }

View File

@@ -35,14 +35,14 @@ use Doctrine\ORM\Mapping\OneToOne;
class Grid class Grid
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[OneToOne(inversedBy: 'grid', cascade: ['persist'])] #[OneToOne(inversedBy: 'grid', cascade: ['persist'])]
private ?PlayedGame $playedGame = null; public ?PlayedGame $playedGame = null;
#[OneToMany(mappedBy: 'grid', targetEntity: GridRow::class, cascade: ['persist'])] #[OneToMany(targetEntity: GridRow::class, mappedBy: 'grid', cascade: ['persist'])]
#[JoinColumn(name: 'grid_row', referencedColumnName: 'id', onDelete: 'CASCADE')] #[JoinColumn(name: 'grid_row', referencedColumnName: 'id', onDelete: 'CASCADE')]
private Collection $gridRow; public private(set) Collection $gridRow;
public function __construct() public function __construct()
@@ -50,30 +50,10 @@ class Grid
$this->gridRow = new ArrayCollection(); $this->gridRow = new ArrayCollection();
} }
public function getId(): ?int
{
return $this->id;
}
public function getPlayedGame(): ?PlayedGame
{
return $this->playedGame;
}
public function setPlayedGame(?PlayedGame $playedGame): void
{
$this->playedGame = $playedGame;
}
public function getGridRow(): Collection
{
return $this->gridRow;
}
public function addGridRow(GridRow $gridRow): void public function addGridRow(GridRow $gridRow): void
{ {
$this->gridRow->add($gridRow); $this->gridRow->add($gridRow);
$gridRow->setGrid($this); $gridRow->grid = $this;
} }
public function removeGridRow(GridRow $gridRow): void public function removeGridRow(GridRow $gridRow): void

View File

@@ -1,5 +1,5 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
/* /**
* This file is part of the SplendidBear Websites' projects. * This file is part of the SplendidBear Websites' projects.
* *
* Copyright (c) 2026 @ www.splendidbear.org * Copyright (c) 2026 @ www.splendidbear.org
@@ -33,38 +33,12 @@ use Doctrine\ORM\Mapping\ManyToOne;
class GridRow class GridRow
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[ManyToOne(cascade: ['persist'], inversedBy: 'gridRow')] #[ManyToOne(cascade: ['persist'], inversedBy: 'gridRow')]
#[JoinColumn(name: 'grid', referencedColumnName: 'id', onDelete: 'CASCADE')] #[JoinColumn(name: 'grid', referencedColumnName: 'id', onDelete: 'CASCADE')]
private ?Grid $grid = null; public ?Grid $grid = null;
#[Column(name: 'grid_col', type: Types::JSON, nullable: false)] #[Column(name: 'grid_col', type: Types::JSON, nullable: false)]
private ?array $gridCol = null; public ?array $gridCol = null;
public function getId(): ?int
{
return $this->id;
}
public function getGrid(): ?Grid
{
return $this->grid;
}
public function setGrid(?Grid $grid): void
{
$this->grid = $grid;
}
public function getGridCol(): ?array
{
return $this->gridCol;
}
public function setGridCol(?array $gridCol): void
{
$this->gridCol = $gridCol;
}
} }

View File

@@ -39,68 +39,68 @@ use Symfony\Component\Uid\Uuid;
class PlayedGame class PlayedGame
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[Column(type: 'uuid', unique: true)] #[Column(type: 'uuid', unique: true)]
private ?Uuid $uuid = null; public ?Uuid $uuid = null;
#[Column(length: 50)] #[Column(length: 50)]
private ?string $gameAssoc = null; public ?string $gameAssoc = null;
#[Column(length: 5, nullable: true)] #[Column(length: 5, nullable: true)]
private ?int $redPoints = null; public ?int $redPoints = null;
#[Column(length: 5, nullable: true)] #[Column(length: 5, nullable: true)]
private ?int $bluePoints = null; public ?int $bluePoints = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?bool $redExplodedBomb = null; public ?bool $redExplodedBomb = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?bool $blueExplodedBomb = null; public ?bool $blueExplodedBomb = null;
#[Column(length: 7, nullable: true)] #[Column(length: 7, nullable: true)]
private ?string $resign = null; public ?string $resign = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?float $redBonusPoints = null; public ?float $redBonusPoints = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?float $blueBonusPoints = null; public ?float $blueBonusPoints = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?array $redBonusStats = null; public ?array $redBonusStats = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?array $blueBonusStats = null; public ?array $blueBonusStats = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $created = null; public ?DateTime $created = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $updated = null; public ?DateTime $updated = null;
#[OneToOne(mappedBy: 'playedGame', cascade: ['persist'])] #[OneToOne(mappedBy: 'playedGame', cascade: ['persist'])]
private ?Grid $grid = null; public ?Grid $grid = null;
#[ManyToOne] #[ManyToOne]
#[JoinColumn(name: 'red_id', referencedColumnName: 'id', nullable: true)] #[JoinColumn(name: 'red_id', referencedColumnName: 'id', nullable: true)]
private ?User $red = null; public ?User $red = null;
#[ManyToOne] #[ManyToOne]
#[JoinColumn(name: 'red_anon', referencedColumnName: 'id', nullable: true)] #[JoinColumn(name: 'red_anon', referencedColumnName: 'id', nullable: true)]
private ?Gamer $redAnon = null; public ?Gamer $redAnon = null;
#[ManyToOne] #[ManyToOne]
#[JoinColumn(name: 'blue_id', referencedColumnName: 'id', nullable: true)] #[JoinColumn(name: 'blue_id', referencedColumnName: 'id', nullable: true)]
private ?User $blue = null; public ?User $blue = null;
#[ManyToOne] #[ManyToOne]
#[JoinColumn(name: 'blue_anon', referencedColumnName: 'id', nullable: true)] #[JoinColumn(name: 'blue_anon', referencedColumnName: 'id', nullable: true)]
private ?Gamer $blueAnon = null; public ?Gamer $blueAnon = null;
#[OneToMany(mappedBy: 'playedGame', targetEntity: Step::class)] #[OneToMany(mappedBy: 'playedGame', targetEntity: Step::class)]
private Collection $steps; public private(set) Collection $steps;
public function __construct() public function __construct()
@@ -108,196 +108,4 @@ class PlayedGame
$this->steps = new ArrayCollection(); $this->steps = new ArrayCollection();
$this->uuid = Uuid::v4(); $this->uuid = Uuid::v4();
} }
public function getId(): ?int
{
return $this->id;
}
public function getUuid(): ?Uuid
{
return $this->uuid;
}
public function setUuid(?Uuid $uuid): void
{
$this->uuid = $uuid;
}
public function getGameAssoc(): ?string
{
return $this->gameAssoc;
}
public function setGameAssoc(?string $gameAssoc): void
{
$this->gameAssoc = $gameAssoc;
}
public function getGrid(): ?Grid
{
return $this->grid;
}
public function setGrid(?Grid $grid): void
{
$this->grid = $grid;
}
public function getRed(): ?User
{
return $this->red;
}
public function setRed(?User $red): void
{
$this->red = $red;
}
public function getRedAnon(): ?Gamer
{
return $this->redAnon;
}
public function setRedAnon(?Gamer $redAnon): void
{
$this->redAnon = $redAnon;
}
public function getBlue(): ?User
{
return $this->blue;
}
public function setBlue(?User $blue): void
{
$this->blue = $blue;
}
public function getBlueAnon(): ?Gamer
{
return $this->blueAnon;
}
public function setBlueAnon(?Gamer $blueAnon): void
{
$this->blueAnon = $blueAnon;
}
public function getRedPoints(): ?int
{
return $this->redPoints;
}
public function setRedPoints(?int $redPoints): void
{
$this->redPoints = $redPoints;
}
public function getBluePoints(): ?int
{
return $this->bluePoints;
}
public function setBluePoints(?int $bluePoints): void
{
$this->bluePoints = $bluePoints;
}
public function getRedExplodedBomb(): ?bool
{
return $this->redExplodedBomb;
}
public function setRedExplodedBomb(?bool $redExplodedBomb): void
{
$this->redExplodedBomb = $redExplodedBomb;
}
public function getBlueExplodedBomb(): ?bool
{
return $this->blueExplodedBomb;
}
public function setBlueExplodedBomb(?bool $blueExplodedBomb): void
{
$this->blueExplodedBomb = $blueExplodedBomb;
}
public function getResign(): ?string
{
return $this->resign;
}
public function setResign(?string $resign): void
{
$this->resign = $resign;
}
public function getRedBonusPoints(): ?float
{
return $this->redBonusPoints;
}
public function setRedBonusPoints(?float $redBonusPoints): void
{
$this->redBonusPoints = $redBonusPoints;
}
public function getBlueBonusPoints(): ?float
{
return $this->blueBonusPoints;
}
public function setBlueBonusPoints(?float $blueBonusPoints): void
{
$this->blueBonusPoints = $blueBonusPoints;
}
public function getRedBonusStats(): ?array
{
return $this->redBonusStats;
}
public function setRedBonusStats(?array $redBonusStats): void
{
$this->redBonusStats = $redBonusStats;
}
public function getBlueBonusStats(): ?array
{
return $this->blueBonusStats;
}
public function setBlueBonusStats(?array $blueBonusStats): void
{
$this->blueBonusStats = $blueBonusStats;
}
public function getCreated(): ?DateTime
{
return $this->created;
}
public function setCreated(?DateTime $created): void
{
$this->created = $created;
}
public function getUpdated(): ?DateTime
{
return $this->updated;
}
public function setUpdated(?DateTime $updated): void
{
$this->updated = $updated;
}
public function getSteps(): Collection
{
return $this->steps;
}
} }

View File

@@ -1,5 +1,5 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
/* /**
* This file is part of the SplendidBear Websites' projects. * This file is part of the SplendidBear Websites' projects.
* *
* Copyright (c) 2026 @ www.splendidbear.org * Copyright (c) 2026 @ www.splendidbear.org
@@ -33,102 +33,26 @@ use Doctrine\ORM\Mapping\ManyToOne;
class Step class Step
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[Column(length: 3)] #[Column(length: 3)]
private ?int $row = null; public ?int $row = null;
#[Column(length: 3)] #[Column(length: 3)]
private ?int $col = null; public ?int $col = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?bool $wBomb = null; public ?bool $wBomb = null;
#[Column(length: 10, nullable: true)] #[Column(length: 10, nullable: true)]
private ?string $player = null; public ?string $player = null;
#[Column(type: Types::JSON, nullable: true)] #[Column(type: Types::JSON, nullable: true)]
private ?array $revealedCells = null; public ?array $revealedCells = null;
#[ManyToOne(inversedBy: 'steps')] #[ManyToOne(inversedBy: 'steps')]
private ?PlayedGame $playedGame = null; public ?PlayedGame $playedGame = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $created = null; public ?DateTime $created = null;
public function getId(): ?int
{
return $this->id;
}
public function getRow(): ?int
{
return $this->row;
}
public function setRow(?int $row): void
{
$this->row = $row;
}
public function getCol(): ?int
{
return $this->col;
}
public function setCol(?int $col): void
{
$this->col = $col;
}
public function getWBomb(): ?bool
{
return $this->wBomb;
}
public function setWBomb(?bool $wBomb): void
{
$this->wBomb = $wBomb;
}
public function getPlayer(): ?string
{
return $this->player;
}
public function setPlayer(?string $player): void
{
$this->player = $player;
}
public function getRevealedCells(): ?array
{
return $this->revealedCells;
}
public function setRevealedCells(?array $revealedCells): void
{
$this->revealedCells = $revealedCells;
}
public function getPlayedGame(): ?PlayedGame
{
return $this->playedGame;
}
public function setPlayedGame(?PlayedGame $playedGame): void
{
$this->playedGame = $playedGame;
}
public function getCreated(): ?DateTime
{
return $this->created;
}
public function setCreated(?DateTime $created): void
{
$this->created = $created;
}
} }

View File

@@ -43,61 +43,53 @@ use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwoFactorInterface, BackupCodeInterface class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwoFactorInterface, BackupCodeInterface
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[Column(length: 180, unique: true)] #[Column(length: 180, unique: true)]
private ?string $username = null; public ?string $username = null;
#[Column] #[Column]
private array $roles = []; public array $roles = [];
#[Column(nullable: true)] #[Column(nullable: true)]
private ?string $password = null; public ?string $password = null;
#[Column(length: 254, unique: true, nullable: true)] #[Column(length: 254, unique: true, nullable: true)]
private ?string $email = null; public ?string $email = null;
#[Column] #[Column]
private bool $isVerified = false; public bool $isVerified = false;
#[Column(length: 64, nullable: true)] #[Column(length: 64, nullable: true)]
private ?string $verificationToken = null; public ?string $verificationToken = null;
#[Column(length: 64, nullable: true)] #[Column(length: 64, nullable: true)]
private ?string $resetToken = null; public ?string $resetToken = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $resetTokenExpiresAt = null; public ?DateTime $resetTokenExpiresAt = null;
#[Column(length: 255, nullable: true)] #[Column(length: 255, nullable: true)]
private ?string $totpSecret = null; public ?string $totpSecret = null;
/** Stored as nullable JSON; the get hook always exposes a non-null array. */
#[Column(type: Types::JSON, nullable: true)] #[Column(type: Types::JSON, nullable: true)]
private ?array $backupCodes = []; public ?array $backupCodes = [] {
get => $this->backupCodes ?? [];
}
#[Column(length: 255, nullable: true)] #[Column(length: 255, nullable: true)]
private ?string $avatarPath = null; public ?string $avatarPath = null;
#[Column(nullable: true)] #[Column(nullable: true)]
private ?bool $consentGiven = null; public ?bool $consentGiven = null;
public function getId(): ?int
{
return $this->id;
}
public function getUsername(): ?string public function getUsername(): ?string
{ {
return $this->username; return $this->username;
} }
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getUserIdentifier(): string public function getUserIdentifier(): string
{ {
return (string)$this->username; return (string)$this->username;
@@ -105,15 +97,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwo
public function getRoles(): array public function getRoles(): array
{ {
$roles = $this->roles; return array_unique([...$this->roles, 'ROLE_USER']);
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
} }
public function getPassword(): ?string public function getPassword(): ?string
@@ -121,82 +105,10 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwo
return $this->password; return $this->password;
} }
public function setPassword(?string $password): self
{
$this->password = $password;
return $this;
}
public function eraseCredentials(): void public function eraseCredentials(): void
{ {
} }
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;
return $this;
}
public function getVerificationToken(): ?string
{
return $this->verificationToken;
}
public function setVerificationToken(?string $verificationToken): self
{
$this->verificationToken = $verificationToken;
return $this;
}
public function getResetToken(): ?string
{
return $this->resetToken;
}
public function setResetToken(?string $resetToken): self
{
$this->resetToken = $resetToken;
return $this;
}
public function getResetTokenExpiresAt(): ?DateTime
{
return $this->resetTokenExpiresAt;
}
public function setResetTokenExpiresAt(?DateTime $resetTokenExpiresAt): self
{
$this->resetTokenExpiresAt = $resetTokenExpiresAt;
return $this;
}
public function getAvatarPath(): ?string
{
return $this->avatarPath;
}
public function setAvatarPath(?string $avatarPath): self
{
$this->avatarPath = $avatarPath;
return $this;
}
public function isTotpAuthenticationEnabled(): bool public function isTotpAuthenticationEnabled(): bool
{ {
return null !== $this->totpSecret; return null !== $this->totpSecret;
@@ -215,46 +127,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwo
return new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, 30, 6); return new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, 30, 6);
} }
public function getTotpSecret(): ?string
{
return $this->totpSecret;
}
public function setTotpSecret(?string $totpSecret): self
{
$this->totpSecret = $totpSecret;
return $this;
}
public function isBackupCode(string $code): bool public function isBackupCode(string $code): bool
{ {
return \in_array($code, $this->backupCodes ?? [], true); return \in_array($code, $this->backupCodes, true);
} }
public function invalidateBackupCode(string $code): void public function invalidateBackupCode(string $code): void
{ {
$this->backupCodes = array_values(array_filter($this->backupCodes ?? [], fn($c) => $c !== $code)); $this->backupCodes = array_values(array_filter($this->backupCodes, fn($c) => $c !== $code));
}
public function getBackupCodes(): array
{
return $this->backupCodes ?? [];
}
public function setBackupCodes(array $backupCodes): self
{
$this->backupCodes = $backupCodes;
return $this;
}
public function isConsentGiven(): ?bool
{
return $this->consentGiven;
}
public function setConsentGiven(?bool $consentGiven): self
{
$this->consentGiven = $consentGiven;
return $this;
} }
} }

View File

@@ -20,6 +20,8 @@ use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne; use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\Table; use Doctrine\ORM\Mapping\Table;
use JsonException;
use RuntimeException;
/** /**
* Class WebAuthnCredential * Class WebAuthnCredential
@@ -36,29 +38,29 @@ use Doctrine\ORM\Mapping\Table;
class WebAuthnCredential class WebAuthnCredential
{ {
#[Id, GeneratedValue, Column] #[Id, GeneratedValue, Column]
private ?int $id = null; public private(set) ?int $id = null;
#[ManyToOne(targetEntity: User::class)] #[ManyToOne(targetEntity: User::class)]
#[JoinColumn(nullable: false, onDelete: 'CASCADE')] #[JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?User $user = null; public ?User $user = null;
#[Column(type: Types::TEXT)] #[Column(type: Types::TEXT)]
private ?string $credentialData = null; public ?string $credentialData = null;
#[Column(length: 255)] #[Column(length: 255)]
private ?string $credentialName = null; public ?string $credentialName = null;
#[Column(type: Types::DATETIME_MUTABLE)] #[Column(type: Types::DATETIME_MUTABLE)]
private ?DateTime $createdAt = null; public ?DateTime $createdAt = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $lastUsedAt = null; public ?DateTime $lastUsedAt = null;
#[Column] #[Column]
private bool $isBackupEligible = false; public bool $isBackupEligible = false;
#[Column] #[Column]
private bool $isBackupAuthenticated = false; public bool $isBackupAuthenticated = false;
public function __construct() public function __construct()
@@ -66,106 +68,27 @@ class WebAuthnCredential
$this->createdAt = new DateTime(); $this->createdAt = new DateTime();
} }
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getCredentialData(): ?string
{
return $this->credentialData;
}
public function setCredentialData(?string $credentialData): self
{
$this->credentialData = $credentialData;
return $this;
}
public function getCredentialName(): ?string
{
return $this->credentialName;
}
public function setCredentialName(?string $credentialName): self
{
$this->credentialName = $credentialName;
return $this;
}
public function getCreatedAt(): ?DateTime
{
return $this->createdAt;
}
public function setCreatedAt(?DateTime $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getLastUsedAt(): ?DateTime
{
return $this->lastUsedAt;
}
public function setLastUsedAt(?DateTime $lastUsedAt): self
{
$this->lastUsedAt = $lastUsedAt;
return $this;
}
public function isBackupEligible(): bool
{
return $this->isBackupEligible;
}
public function setBackupEligible(bool $isBackupEligible): self
{
$this->isBackupEligible = $isBackupEligible;
return $this;
}
public function isBackupAuthenticated(): bool
{
return $this->isBackupAuthenticated;
}
public function setBackupAuthenticated(bool $isBackupAuthenticated): self
{
$this->isBackupAuthenticated = $isBackupAuthenticated;
return $this;
}
public function getPublicKeyCredentialSource() public function getPublicKeyCredentialSource()
{ {
// Return the raw credential data (JSON decoded)
if ($this->credentialData === null) { if ($this->credentialData === null) {
return null; return null;
} }
return json_decode($this->credentialData, true);
try {
return json_decode($this->credentialData, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new RuntimeException("Unable to parse JSON: {$e->getMessage()}");
}
} }
public function setPublicKeyCredentialSource($source): self public function setPublicKeyCredentialSource($source): self
{ {
// Handle both array and object input try {
if (is_array($source)) { $this->credentialData = is_array($source) ? json_encode($source, JSON_THROW_ON_ERROR) : (string)$source;
$this->credentialData = json_encode($source); } catch (JsonException $e) {
} else { throw new RuntimeException("Unable to encode JSON: {$e->getMessage()}");
$this->credentialData = (string)$source;
} }
return $this; return $this;
} }
} }

View File

@@ -0,0 +1,66 @@
<?php declare(strict_types=1);
/**
* This file is part of the SplendidBear Websites' projects.
*
* Copyright (c) 2026 @ www.splendidbear.org
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Class Version20260420064216
*
* Realign each PostgreSQL IDENTITY counter to MAX(id)+1.
*
* Required because Version20260419195925 converted the ID columns from sequences to
* GENERATED BY DEFAULT AS IDENTITY (DBAL 4 / ORM 3 default), but PostgreSQL starts
* the new IDENTITY counter at 1 instead of inheriting the old sequence's last_value.
* The first INSERT on every table thus produced a duplicate-key collision.
*
* @package App\Migrations
* @author Lang <https://www.splendidbear.org>
* @category Class
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
* @link www.splendidbear.org
* @since 2026. 04. 20.
*/
final class Version20260420064216 extends AbstractMigration
{
private const TABLES = [
'played_game',
'grid',
'grid_row',
'step',
'app_user',
'app_webauthn_credential',
'gamer',
'contact_messages',
];
public function getDescription(): string
{
return 'Realign IDENTITY counters to MAX(id)+1 after the sequence-to-identity conversion.';
}
public function up(Schema $schema): void
{
foreach (self::TABLES as $table) {
$this->addSql(sprintf(
"SELECT setval(pg_get_serial_sequence('%s', 'id'), COALESCE((SELECT MAX(id) FROM %s), 0) + 1, false)",
$table,
$table,
));
}
}
public function down(Schema $schema): void
{
$this->throwIrreversibleMigrationException('Identity counter realignment cannot be reversed safely.');
}
}

View File

@@ -269,7 +269,7 @@ class PlayedGameRepository extends ServiceEntityRepository
COALESCE(SUM(CASE WHEN g.red_id = :uid THEN g.red_points ELSE g.blue_points END), 0) AS total_pts COALESCE(SUM(CASE WHEN g.red_id = :uid THEN g.red_points ELSE g.blue_points END), 0) AS total_pts
FROM played_game g FROM played_game g
WHERE (g.red_id = :uid OR g.blue_id = :uid)', WHERE (g.red_id = :uid OR g.blue_id = :uid)',
['uid' => $user->getId()], ['uid' => $user->id],
)->fetchAssociative(); )->fetchAssociative();
return (int) ($result['total_pts'] ?? 0); return (int) ($result['total_pts'] ?? 0);
@@ -289,7 +289,7 @@ class PlayedGameRepository extends ServiceEntityRepository
(g.red_id = :uid AND g.red_points IS NOT NULL) (g.red_id = :uid AND g.red_points IS NOT NULL)
OR (g.blue_id = :uid AND g.blue_points IS NOT NULL) OR (g.blue_id = :uid AND g.blue_points IS NOT NULL)
)', )',
['uid' => $user->getId()], ['uid' => $user->id],
)->fetchAssociative(); )->fetchAssociative();
if (!$result || (int) $result['total_games'] === 0) { if (!$result || (int) $result['total_games'] === 0) {
@@ -306,7 +306,7 @@ class PlayedGameRepository extends ServiceEntityRepository
*/ */
public function findBonusStatsForUser(User $user): array public function findBonusStatsForUser(User $user): array
{ {
$userId = $user->getId(); $userId = $user->id;
$qb = $this->createQueryBuilder('g'); $qb = $this->createQueryBuilder('g');
$qb->where($qb->expr()->orX( $qb->where($qb->expr()->orX(
$qb->expr()->eq('g.red', ':u'), $qb->expr()->eq('g.red', ':u'),
@@ -323,10 +323,10 @@ class PlayedGameRepository extends ServiceEntityRepository
$gameCount = 0; $gameCount = 0;
foreach ($games as $game) { foreach ($games as $game) {
$isRed = $game->getRed()?->getId() === $userId; $isRed = $game->red?->id === $userId;
$totalBonusPoints += (float) (($isRed ? $game->getRedBonusPoints() : $game->getBlueBonusPoints()) ?? 0.0); $totalBonusPoints += (float) (($isRed ? $game->redBonusPoints : $game->blueBonusPoints) ?? 0.0);
$stats = ($isRed ? $game->getRedBonusStats() : $game->getBlueBonusStats()) ?? []; $stats = ($isRed ? $game->redBonusStats : $game->blueBonusStats) ?? [];
$bestChain = max($bestChain, (int) ($stats['chainBest'] ?? 0)); $bestChain = max($bestChain, (int) ($stats['chainBest'] ?? 0));
$totalBlindHits += (int) ($stats['blindHits'] ?? 0); $totalBlindHits += (int) ($stats['blindHits'] ?? 0);
$totalEdgeMines += (int) ($stats['edgeMines'] ?? 0); $totalEdgeMines += (int) ($stats['edgeMines'] ?? 0);

View File

@@ -42,18 +42,19 @@ class StepRepository extends ServiceEntityRepository
public function findMostRecent(PlayedGame $playedGame): ?Step public function findMostRecent(PlayedGame $playedGame): ?Step
{ {
try { try {
return $this->createQueryBuilder('s') return $this
->andWhere('s.playedGame = :game') ->createQueryBuilder('s')
->setParameter('game', $playedGame) ->andWhere('s.playedGame = :game')
->orderBy('s.created', 'DESC') ->setParameter('game', $playedGame)
->setMaxResults(1) ->orderBy('s.created', 'DESC')
->getQuery() ->setMaxResults(1)
->getOneOrNullResult(); ->getQuery()
->getOneOrNullResult();
} catch (NonUniqueResultException $e) { } catch (NonUniqueResultException $e) {
throw new RuntimeException( throw new RuntimeException(
sprintf( sprintf(
'Expected at most one result for the most recent step of game ID %d, but got multiple.', 'Expected at most one result for the most recent step of game ID %d, but got multiple.',
$playedGame->getId(), $playedGame->id,
), ),
0, 0,
$e, $e,
@@ -64,15 +65,16 @@ class StepRepository extends ServiceEntityRepository
public function findMostRecentForPlayer(PlayedGame $playedGame, string $player): ?Step public function findMostRecentForPlayer(PlayedGame $playedGame, string $player): ?Step
{ {
try { try {
return $this->createQueryBuilder('s') return $this
->andWhere('s.playedGame = :game') ->createQueryBuilder('s')
->andWhere('s.player = :player') ->andWhere('s.playedGame = :game')
->setParameter('game', $playedGame) ->andWhere('s.player = :player')
->setParameter('player', $player) ->setParameter('game', $playedGame)
->orderBy('s.created', 'DESC') ->setParameter('player', $player)
->setMaxResults(1) ->orderBy('s.created', 'DESC')
->getQuery() ->setMaxResults(1)
->getOneOrNullResult(); ->getQuery()
->getOneOrNullResult();
} catch (NonUniqueResultException $e) { } catch (NonUniqueResultException $e) {
throw new RuntimeException( throw new RuntimeException(
'Expected at most one result for the most recent step of player "%s" in game ID %d, but got multiple.', 'Expected at most one result for the most recent step of player "%s" in game ID %d, but got multiple.',

View File

@@ -93,7 +93,7 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
} }
$user->setPassword($newHashedPassword); $user->password = $newHashedPassword;
$this->getEntityManager()->persist($user); $this->getEntityManager()->persist($user);
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
} }

View File

@@ -16,6 +16,15 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
/** /**
* Class WebAuthnCredentialRepository
*
* @package App\Repository
* @author Lang <https://www.splendidbear.org>
* @category Class
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
* @link www.splendidbear.org
* @since 2026. 04. 12.
*
* @extends ServiceEntityRepository<WebAuthnCredential> * @extends ServiceEntityRepository<WebAuthnCredential>
* *
* @method WebAuthnCredential|null find($id, $lockMode = null, $lockVersion = null) * @method WebAuthnCredential|null find($id, $lockMode = null, $lockVersion = null)

View File

@@ -33,7 +33,7 @@ final class UserChecker implements UserCheckerInterface
return; return;
} }
if (!$user->isVerified()) { if (!$user->isVerified) {
throw new CustomUserMessageAuthenticationException( throw new CustomUserMessageAuthenticationException(
'Please verify your email address before signing in. Check your inbox for the activation link.' 'Please verify your email address before signing in. Check your inbox for the activation link.'
); );

View File

@@ -54,7 +54,7 @@ class BattleCardGenerator
public function generate(PlayedGame $game): string public function generate(PlayedGame $game): string
{ {
$path = $this->cachePath((int)$game->getId()); $path = $this->cachePath((int)$game->id);
// Always regenerate to ensure bonus points are included // Always regenerate to ensure bonus points are included
if (is_file($path)) { if (is_file($path)) {
@@ -108,13 +108,13 @@ class BattleCardGenerator
imageline($im, self::WIDTH / 2, 110, self::WIDTH / 2, self::HEIGHT - 80, $divider); imageline($im, self::WIDTH / 2, 110, self::WIDTH / 2, self::HEIGHT - 80, $divider);
/** Resolve names*/ /** Resolve names*/
$redName = $game->getRed()?->getUsername() $redName = $game->red?->getUsername()
?? ($game->getRedAnon() !== null ? 'Anonymous' : 'Guest'); ?? ($game->redAnon !== null ? 'Anonymous' : 'Guest');
$blueName = $game->getBlue()?->getUsername() $blueName = $game->blue?->getUsername()
?? ($game->getBlueAnon() !== null ? 'Anonymous' : 'Guest'); ?? ($game->blueAnon !== null ? 'Anonymous' : 'Guest');
$redPts = $game->getRedPoints(); $redPts = $game->redPoints;
$bluePts = $game->getBluePoints(); $bluePts = $game->bluePoints;
$resign = $game->getResign(); $resign = $game->resign;
/** Winner*/ /** Winner*/
$winner = null; $winner = null;
@@ -135,8 +135,8 @@ class BattleCardGenerator
$this->centeredText($im, 'BLUE', 16, 980, 130, $blue); $this->centeredText($im, 'BLUE', 16, 980, 130, $blue);
/** Draw avatars below the team labels (moved down by 60px total: 200 → 260)*/ /** Draw avatars below the team labels (moved down by 60px total: 200 → 260)*/
$redAvatar = $game->getRed()?->getAvatarPath(); $redAvatar = $game->red?->avatarPath;
$blueAvatar = $game->getBlue()?->getAvatarPath(); $blueAvatar = $game->blue?->avatarPath;
$this->drawAvatar($im, $redAvatar, 220, 260, $red, $redName); $this->drawAvatar($im, $redAvatar, 220, 260, $red, $redName);
$this->drawAvatar($im, $blueAvatar, 980, 260, $blue, $blueName); $this->drawAvatar($im, $blueAvatar, 980, 260, $blue, $blueName);
@@ -156,8 +156,8 @@ class BattleCardGenerator
$this->centeredText($im, $scoreText, 72, self::WIDTH / 2, 390, $white); $this->centeredText($im, $scoreText, 72, self::WIDTH / 2, 390, $white);
/** Bonus points below score*/ /** Bonus points below score*/
$redBonusPoints = $game->getRedBonusPoints() ?? 0; $redBonusPoints = $game->redBonusPoints ?? 0;
$blueBonusPoints = $game->getBlueBonusPoints() ?? 0; $blueBonusPoints = $game->blueBonusPoints ?? 0;
$bonusText = number_format((float)$redBonusPoints, 1, '.', '') . ' * : * ' . number_format((float)$blueBonusPoints, 1, '.', ''); $bonusText = number_format((float)$redBonusPoints, 1, '.', '') . ' * : * ' . number_format((float)$blueBonusPoints, 1, '.', '');
$this->centeredText($im, $bonusText, 24, self::WIDTH / 2, 425, $gold); $this->centeredText($im, $bonusText, 24, self::WIDTH / 2, 425, $gold);

View File

@@ -45,8 +45,8 @@ readonly final class SendContactMailService
new TemplatedEmail() new TemplatedEmail()
->from('noreply@mineseeker.hu') ->from('noreply@mineseeker.hu')
->to($this->appContactMailAddress) ->to($this->appContactMailAddress)
->replyTo($contactMessage->getEmail()) ->replyTo($contactMessage->email)
->subject('New Contact Message from ' . $contactMessage->getName()) ->subject('New Contact Message from ' . $contactMessage->name)
->htmlTemplate('emails/contact_notification.html.twig') ->htmlTemplate('emails/contact_notification.html.twig')
->context(['message' => $contactMessage]) ->context(['message' => $contactMessage])
); );

View File

@@ -70,10 +70,10 @@ readonly final class ResolveUserNamesService
private function resolveOpponentName(PlayedGame $game, string $myUserName): string private function resolveOpponentName(PlayedGame $game, string $myUserName): string
{ {
$redName = $game->getRed()?->getUsername(); $redName = $game->red?->getUsername();
$blueName = $game->getBlue()?->getUsername(); $blueName = $game->blue?->getUsername();
$redAnonName = $game->getRedAnon()?->getUserName(); $redAnonName = $game->redAnon?->userName;
$blueAnonName = $game->getBlueAnon()?->getUserName(); $blueAnonName = $game->blueAnon?->userName;
$isRed = $myUserName === $redName || $myUserName === $redAnonName; $isRed = $myUserName === $redName || $myUserName === $redAnonName;
$isBlue = $myUserName === $blueName || $myUserName === $blueAnonName; $isBlue = $myUserName === $blueName || $myUserName === $blueAnonName;

View File

@@ -39,11 +39,11 @@ readonly class WebAuthnService
public function saveCredential(User $user, array $credentialData, string $name): WebAuthnCredential public function saveCredential(User $user, array $credentialData, string $name): WebAuthnCredential
{ {
$credential = new WebAuthnCredential(); $credential = new WebAuthnCredential();
$credential->setUser($user); $credential->user = $user;
$credential->setCredentialData(json_encode($credentialData)); $credential->credentialData = json_encode($credentialData);
$credential->setCredentialName($name); $credential->credentialName = $name;
$credential->setBackupEligible($credentialData['isBackupEligible'] ?? false); $credential->isBackupEligible = $credentialData['isBackupEligible'] ?? false;
$credential->setBackupAuthenticated($credentialData['isBackupAuthenticated'] ?? false); $credential->isBackupAuthenticated = $credentialData['isBackupAuthenticated'] ?? false;
$this->entityManager->persist($credential); $this->entityManager->persist($credential);
$this->entityManager->flush(); $this->entityManager->flush();
@@ -60,7 +60,7 @@ readonly class WebAuthnService
{ {
$credential = $this->credentialRepository->find($id); $credential = $this->credentialRepository->find($id);
if ($credential === null || $credential->getUser() !== $user) { if ($credential === null || $credential->user !== $user) {
return false; return false;
} }
@@ -74,11 +74,11 @@ readonly class WebAuthnService
{ {
$credential = $this->credentialRepository->find($id); $credential = $this->credentialRepository->find($id);
if ($credential === null || $credential->getUser() !== $user) { if ($credential === null || $credential->user !== $user) {
return false; return false;
} }
$credential->setCredentialName($name); $credential->credentialName = $name;
$this->entityManager->flush(); $this->entityManager->flush();
return true; return true;
@@ -99,7 +99,7 @@ readonly class WebAuthnService
$sources = []; $sources = [];
foreach ($credentials as $credential) { foreach ($credentials as $credential) {
$data = $credential->getCredentialData(); $data = $credential->credentialData;
if ($data === null) { if ($data === null) {
continue; continue;
@@ -109,7 +109,7 @@ readonly class WebAuthnService
$sources[] = json_decode($data, true, 512, JSON_THROW_ON_ERROR); $sources[] = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) { } catch (JsonException $e) {
throw new RuntimeException( throw new RuntimeException(
"Failed to decode credential data for credential ID $credential->getId(): $e->getMessage()", "Failed to decode credential data for credential ID {$credential->id}: {$e->getMessage()}",
); );
} }
} }
@@ -122,13 +122,13 @@ readonly class WebAuthnService
$credentials = $this->credentialRepository->findByUser($user); $credentials = $this->credentialRepository->findByUser($user);
foreach ($credentials as $credential) { foreach ($credentials as $credential) {
$data = json_decode($credential->getCredentialData() ?? '{}', true); $data = json_decode($credential->credentialData ?? '{}', true);
if (($data['id'] ?? null) !== $credentialId) { if (($data['id'] ?? null) !== $credentialId) {
continue; continue;
} }
$credential->setLastUsedAt(new DateTime()); $credential->lastUsedAt = new DateTime();
$this->entityManager->flush(); $this->entityManager->flush();
break; break;
} }
@@ -139,13 +139,13 @@ readonly class WebAuthnService
$allCredentials = $this->credentialRepository->findAll(); $allCredentials = $this->credentialRepository->findAll();
foreach ($allCredentials as $credential) { foreach ($allCredentials as $credential) {
$data = json_decode($credential->getCredentialData() ?? '{}', true); $data = json_decode($credential->credentialData ?? '{}', true);
if (($data['id'] ?? null) !== $credentialId) { if (($data['id'] ?? null) !== $credentialId) {
continue; continue;
} }
return $credential->getUser(); return $credential->user;
} }
return null; return null;

View File

@@ -80,8 +80,8 @@ class RpcManager implements RpcManagerInterface
$revealedCells = $this->aggregateRevealedCells($playedGame); $revealedCells = $this->aggregateRevealedCells($playedGame);
try { try {
$redPoints = $playedGame->getRedPoints() ?? 0; $redPoints = $playedGame->redPoints ?? 0;
$bluePoints = $playedGame->getBluePoints() ?? 0; $bluePoints = $playedGame->bluePoints ?? 0;
$gameFinished = $redPoints > 25 || $bluePoints > 25; $gameFinished = $redPoints > 25 || $bluePoints > 25;
return base64_encode(json_encode([ return base64_encode(json_encode([
@@ -91,10 +91,10 @@ class RpcManager implements RpcManagerInterface
'mostRecentStep' => $this->getMostRecentStep($playedGame), 'mostRecentStep' => $this->getMostRecentStep($playedGame),
'redPoints' => $redPoints, 'redPoints' => $redPoints,
'bluePoints' => $bluePoints, 'bluePoints' => $bluePoints,
'redBonusPoints' => $playedGame->getRedBonusPoints() ?? 0, 'redBonusPoints' => $playedGame->redBonusPoints ?? 0,
'blueBonusPoints' => $playedGame->getBlueBonusPoints() ?? 0, 'blueBonusPoints' => $playedGame->blueBonusPoints ?? 0,
'redBonusStats' => $playedGame->getRedBonusStats() ?? [], 'redBonusStats' => $playedGame->redBonusStats ?? [],
'blueBonusStats' => $playedGame->getBlueBonusStats() ?? [], 'blueBonusStats' => $playedGame->blueBonusStats ?? [],
'gameFinished' => $gameFinished, 'gameFinished' => $gameFinished,
], JSON_THROW_ON_ERROR)); ], JSON_THROW_ON_ERROR));
} catch (JsonException $e) { } catch (JsonException $e) {
@@ -131,19 +131,19 @@ class RpcManager implements RpcManagerInterface
try { try {
foreach ($grid2d as $row) { foreach ($grid2d as $row) {
$gridRow = new GridRow(); $gridRow = new GridRow();
$gridRow->setGridCol($row); $gridRow->gridCol = $row;
$gridRow->setGrid($grid); $gridRow->grid = $grid;
$this->em->persist($gridRow); $this->em->persist($gridRow);
} }
$grid->setPlayedGame($playedGame); $grid->playedGame = $playedGame;
$this->em->persist($grid); $this->em->persist($grid);
$playedGame->setGameAssoc($gameAssoc); $playedGame->gameAssoc = $gameAssoc;
$playedGame->setUuid(Uuid::fromString($gameAssoc)); $playedGame->uuid = Uuid::fromString($gameAssoc);
$playedGame->setGrid($grid); $playedGame->grid = $grid;
$playedGame->setCreated(new DateTime()); $playedGame->created = new DateTime();
$playedGame->setUpdated(new DateTime()); $playedGame->updated = new DateTime();
$this->em->persist($playedGame); $this->em->persist($playedGame);
$this->em->flush(); $this->em->flush();
@@ -212,12 +212,12 @@ class RpcManager implements RpcManagerInterface
{ {
$all = []; $all = [];
foreach ($playedGame->getSteps() as $step) { foreach ($playedGame->steps as $step) {
if (null === $step->getRevealedCells()) { if (null === $step->revealedCells) {
continue; continue;
} }
$player = $step->getPlayer(); $player = $step->player;
foreach ($step->getRevealedCells() as $cell) { foreach ($step->revealedCells as $cell) {
$all[] = array_merge($cell, ['player' => $player]); $all[] = array_merge($cell, ['player' => $player]);
} }
} }
@@ -250,19 +250,19 @@ class RpcManager implements RpcManagerInterface
} }
return [ return [
'player' => $step->getPlayer(), 'player' => $step->player,
'row' => (int)$step->getRow(), 'row' => (int)$step->row,
'col' => (int)$step->getCol(), 'col' => (int)$step->col,
]; ];
} }
private function getUserCollection(PlayedGame $playedGame): array private function getUserCollection(PlayedGame $playedGame): array
{ {
return [ return [
'red' => null !== $playedGame->getRed() ? $playedGame->getRed()->getUsername() : '', 'red' => null !== $playedGame->red ? $playedGame->red->getUsername() : '',
'blue' => null !== $playedGame->getBlue() ? $playedGame->getBlue()->getUsername() : '', 'blue' => null !== $playedGame->blue ? $playedGame->blue->getUsername() : '',
'redAnon' => null !== $playedGame->getRedAnon() ? $playedGame->getRedAnon()->getUserName() : '', 'redAnon' => null !== $playedGame->redAnon ? $playedGame->redAnon->userName : '',
'blueAnon' => null !== $playedGame->getBlueAnon() ? $playedGame->getBlueAnon()->getUserName() : '', 'blueAnon' => null !== $playedGame->blueAnon ? $playedGame->blueAnon->userName : '',
]; ];
} }
} }

View File

@@ -96,7 +96,7 @@ readonly class TopicManager implements TopicManagerInterface
// ── Lobby updates ────────────────────────────────────────────────── // ── Lobby updates ──────────────────────────────────────────────────
if ($count === 1) { if ($count === 1) {
// One player waiting — mark as active and announce to the lobby // One player waiting — mark as active and announce to the lobby
$playedGame->setUpdated(new DateTime()); $playedGame->updated = new DateTime();
$this->em->persist($playedGame); $this->em->persist($playedGame);
$this->em->flush(); $this->em->flush();
@@ -106,7 +106,7 @@ readonly class TopicManager implements TopicManagerInterface
'action' => 'join', 'action' => 'join',
'gameAssoc' => $gameAssoc, 'gameAssoc' => $gameAssoc,
'name' => $displayName, 'name' => $displayName,
'since' => $playedGame->getCreated()?->format(DateTimeInterface::ATOM) ?? '', 'since' => $playedGame->created?->format(DateTimeInterface::ATOM) ?? '',
]); ]);
} elseif ($count === 2) { } elseif ($count === 2) {
// Both players joined — remove from lobby // Both players joined — remove from lobby
@@ -122,7 +122,7 @@ readonly class TopicManager implements TopicManagerInterface
if (null !== $playedGame) { if (null !== $playedGame) {
$users = $this->getUserCollection($playedGame); $users = $this->getUserCollection($playedGame);
if ($this->getPlayerCount($users) === 1) { if ($this->getPlayerCount($users) === 1) {
$playedGame->setUpdated(new DateTime('2000-01-01 00:00:00')); $playedGame->updated = new DateTime('2000-01-01 00:00:00');
$this->em->persist($playedGame); $this->em->persist($playedGame);
$this->em->flush(); $this->em->flush();
$this->publishToLobby(['action' => 'leave', 'gameAssoc' => $gameAssoc]); $this->publishToLobby(['action' => 'leave', 'gameAssoc' => $gameAssoc]);
@@ -194,8 +194,8 @@ readonly class TopicManager implements TopicManagerInterface
$minesFound = count(array_filter($revealedCells, static fn($c) => 'm' === $c['value'])); $minesFound = count(array_filter($revealedCells, static fn($c) => 'm' === $c['value']));
$safeCellsFound = count(array_filter($revealedCells, static fn($c) => 'm' !== $c['value'])); $safeCellsFound = count(array_filter($revealedCells, static fn($c) => 'm' !== $c['value']));
$redPoints = ($playedGame->getRedPoints() ?? 0) + ('red' === $player ? $minesFound : 0); $redPoints = ($playedGame->redPoints ?? 0) + ('red' === $player ? $minesFound : 0);
$bluePoints = ($playedGame->getBluePoints() ?? 0) + ('blue' === $player ? $minesFound : 0); $bluePoints = ($playedGame->bluePoints ?? 0) + ('blue' === $player ? $minesFound : 0);
$gameOver = $redPoints > 25 || $bluePoints > 25; $gameOver = $redPoints > 25 || $bluePoints > 25;
/** Calculate bonus points and stats */ /** Calculate bonus points and stats */
@@ -266,7 +266,7 @@ readonly class TopicManager implements TopicManagerInterface
private function loadGrid(string $gameAssoc): array private function loadGrid(string $gameAssoc): array
{ {
$playedGame = $this->getPlayedGame($gameAssoc); $playedGame = $this->getPlayedGame($gameAssoc);
$gridEntity = $playedGame?->getGrid(); $gridEntity = $playedGame?->grid;
if (null === $gridEntity) { if (null === $gridEntity) {
return []; return [];
@@ -274,8 +274,8 @@ readonly class TopicManager implements TopicManagerInterface
$grid = []; $grid = [];
/** @var GridRow $row */ /** @var GridRow $row */
foreach ($gridEntity->getGridRow() as $row) { foreach ($gridEntity->gridRow as $row) {
$grid[] = $row->getGridCol(); $grid[] = $row->gridCol;
} }
return $grid; return $grid;
@@ -293,7 +293,7 @@ readonly class TopicManager implements TopicManagerInterface
int $bluePoints int $bluePoints
): array { ): array {
/** Initialize or load existing bonus stats */ /** Initialize or load existing bonus stats */
$redBonusStats = $playedGame->getRedBonusStats() ?? [ $redBonusStats = $playedGame->redBonusStats ?? [
'blindHits' => 0, 'blindHits' => 0,
'chainBest' => 0, 'chainBest' => 0,
'chainCurrent' => 0, 'chainCurrent' => 0,
@@ -301,7 +301,7 @@ readonly class TopicManager implements TopicManagerInterface
'edgeMines' => 0, 'edgeMines' => 0,
'biggestReveal' => 0, 'biggestReveal' => 0,
]; ];
$blueBonusStats = $playedGame->getBlueBonusStats() ?? [ $blueBonusStats = $playedGame->blueBonusStats ?? [
'blindHits' => 0, 'blindHits' => 0,
'chainBest' => 0, 'chainBest' => 0,
'chainCurrent' => 0, 'chainCurrent' => 0,
@@ -310,8 +310,8 @@ readonly class TopicManager implements TopicManagerInterface
'biggestReveal' => 0, 'biggestReveal' => 0,
]; ];
$redBonusPoints = $playedGame->getRedBonusPoints() ?? 0; $redBonusPoints = $playedGame->redBonusPoints ?? 0;
$blueBonusPoints = $playedGame->getBlueBonusPoints() ?? 0; $blueBonusPoints = $playedGame->blueBonusPoints ?? 0;
$isRed = 'red' === $player; $isRed = 'red' === $player;
$currentStats = $isRed ? $redBonusStats : $blueBonusStats; $currentStats = $isRed ? $redBonusStats : $blueBonusStats;
@@ -376,10 +376,10 @@ readonly class TopicManager implements TopicManagerInterface
} }
/** Persist updated stats to the database */ /** Persist updated stats to the database */
$playedGame->setRedBonusStats($redBonusStats); $playedGame->redBonusStats = $redBonusStats;
$playedGame->setBlueBonusStats($blueBonusStats); $playedGame->blueBonusStats = $blueBonusStats;
$playedGame->setRedBonusPoints($redBonusPoints); $playedGame->redBonusPoints = $redBonusPoints;
$playedGame->setBlueBonusPoints($blueBonusPoints); $playedGame->blueBonusPoints = $blueBonusPoints;
$this->em->persist($playedGame); $this->em->persist($playedGame);
return [ return [
@@ -538,8 +538,8 @@ readonly class TopicManager implements TopicManagerInterface
private function buildRevealedMap(PlayedGame $playedGame): array private function buildRevealedMap(PlayedGame $playedGame): array
{ {
$map = []; $map = [];
foreach ($playedGame->getSteps() as $step) { foreach ($playedGame->steps as $step) {
foreach ($step->getRevealedCells() ?? [] as $cell) { foreach ($step->revealedCells ?? [] as $cell) {
$map[$cell['row'] . ',' . $cell['col']] = true; $map[$cell['row'] . ',' . $cell['col']] = true;
} }
} }
@@ -583,7 +583,7 @@ readonly class TopicManager implements TopicManagerInterface
private function saveResignToDb(string $gameAssoc, string $color): void private function saveResignToDb(string $gameAssoc, string $color): void
{ {
$playedGame = $this->getPlayedGame($gameAssoc); $playedGame = $this->getPlayedGame($gameAssoc);
$playedGame->setResign($color); $playedGame->resign = $color;
$this->em->persist($playedGame); $this->em->persist($playedGame);
$this->em->flush(); $this->em->flush();
} }
@@ -601,32 +601,32 @@ readonly class TopicManager implements TopicManagerInterface
$playedGame = $this->getPlayedGame($gameAssoc); $playedGame = $this->getPlayedGame($gameAssoc);
$step = new Step(); $step = new Step();
$step->setRow($event['coords'][0]); $step->row = $event['coords'][0];
$step->setCol($event['coords'][1]); $step->col = $event['coords'][1];
$step->setWBomb((bool)$event['bomb']); $step->wBomb = (bool)$event['bomb'];
$step->setPlayer($player); $step->player = $player;
$step->setRevealedCells($revealedCells); $step->revealedCells = $revealedCells;
$step->setPlayedGame($playedGame); $step->playedGame = $playedGame;
$step->setCreated(new DateTime()); $step->created = new DateTime();
$this->em->persist($step); $this->em->persist($step);
$playedGame->setRedPoints($redPoints); $playedGame->redPoints = $redPoints;
$playedGame->setBluePoints($bluePoints); $playedGame->bluePoints = $bluePoints;
if ((bool)$event['bomb']) { if ((bool)$event['bomb']) {
if ('red' === $player) { if ('red' === $player) {
$playedGame->setRedExplodedBomb(true); $playedGame->redExplodedBomb = true;
} elseif ('blue' === $player) { } elseif ('blue' === $player) {
$playedGame->setBlueExplodedBomb(true); $playedGame->blueExplodedBomb = true;
} }
} }
$playedGame->setUpdated(new DateTime()); $playedGame->updated = new DateTime();
/** Bonus data is already persisted in calculateBonuses, but we ensure it's up to date */ /** Bonus data is already persisted in calculateBonuses, but we ensure it's up to date */
if (!empty($bonusData)) { if (!empty($bonusData)) {
$playedGame->setRedBonusPoints($bonusData['redBonusPoints']); $playedGame->redBonusPoints = $bonusData['redBonusPoints'];
$playedGame->setBlueBonusPoints($bonusData['blueBonusPoints']); $playedGame->blueBonusPoints = $bonusData['blueBonusPoints'];
$playedGame->setRedBonusStats($bonusData['redBonusStats']); $playedGame->redBonusStats = $bonusData['redBonusStats'];
$playedGame->setBlueBonusStats($bonusData['blueBonusStats']); $playedGame->blueBonusStats = $bonusData['blueBonusStats'];
} }
$this->em->persist($playedGame); $this->em->persist($playedGame);
@@ -658,11 +658,11 @@ readonly class TopicManager implements TopicManagerInterface
try { try {
if ($count === 1) { if ($count === 1) {
$random = random_int(0, 1); $random = random_int(0, 1);
!$random ? $playedGame->setRed($user) : $playedGame->setBlue($user); !$random ? $playedGame->red = $user : $playedGame->blue = $user;
} else { } else {
null === $playedGame->getRed() && null === $playedGame->getRedAnon() null === $playedGame->red && null === $playedGame->redAnon
? $playedGame->setRed($user) ? $playedGame->red = $user
: $playedGame->setBlue($user); : $playedGame->blue = $user;
} }
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->error($e->getMessage()); $this->logger->error($e->getMessage());
@@ -673,21 +673,21 @@ readonly class TopicManager implements TopicManagerInterface
{ {
try { try {
$anon = new Gamer(); $anon = new Gamer();
$anon->setUserName($userName); $anon->userName = $userName;
$anon->setIp($this->requestStack->getCurrentRequest()->getClientIp()); $anon->ip = $this->requestStack->getCurrentRequest()->getClientIp();
$anon->setCountry($this->extractCountry()); $anon->country = $this->extractCountry();
$anon->setUserAgent($this->requestStack->getCurrentRequest()->headers->get('User-Agent')); $anon->userAgent = $this->requestStack->getCurrentRequest()->headers->get('User-Agent');
$anon->setConnTimestamp(new DateTime()); $anon->connTimestamp = new DateTime();
$this->em->persist($anon); $this->em->persist($anon);
if ($count === 1) { if ($count === 1) {
$random = random_int(0, 1); $random = random_int(0, 1);
!$random ? $playedGame->setRedAnon($anon) : $playedGame->setBlueAnon($anon); !$random ? $playedGame->redAnon = $anon : $playedGame->blueAnon = $anon;
} else { } else {
null === $playedGame->getRed() && null === $playedGame->getRedAnon() null === $playedGame->red && null === $playedGame->redAnon
? $playedGame->setRedAnon($anon) ? $playedGame->redAnon = $anon
: $playedGame->setBlueAnon($anon); : $playedGame->blueAnon = $anon;
} }
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->error($e->getMessage()); $this->logger->error($e->getMessage());
@@ -696,19 +696,19 @@ readonly class TopicManager implements TopicManagerInterface
private function getUserCollection(PlayedGame $playedGame): array private function getUserCollection(PlayedGame $playedGame): array
{ {
$redUser = $playedGame->getRed(); $redUser = $playedGame->red;
$blueUser = $playedGame->getBlue(); $blueUser = $playedGame->blue;
return [ return [
'red' => null !== $redUser ? $redUser->getUsername() : '', 'red' => null !== $redUser ? $redUser->getUsername() : '',
'blue' => null !== $blueUser ? $blueUser->getUsername() : '', 'blue' => null !== $blueUser ? $blueUser->getUsername() : '',
'redAnon' => null !== $playedGame->getRedAnon() ? $playedGame->getRedAnon()->getUserName() : '', 'redAnon' => null !== $playedGame->redAnon ? $playedGame->redAnon->userName : '',
'blueAnon' => null !== $playedGame->getBlueAnon() ? $playedGame->getBlueAnon()->getUserName() : '', 'blueAnon' => null !== $playedGame->blueAnon ? $playedGame->blueAnon->userName : '',
'redAvatar' => null !== $redUser && null !== $redUser->getAvatarPath() 'redAvatar' => null !== $redUser && null !== $redUser->avatarPath
? $this->cacheManager->generateUrl($redUser->getAvatarPath(), 'avatar_thumb') ? $this->cacheManager->generateUrl($redUser->avatarPath, 'avatar_thumb')
: null, : null,
'blueAvatar' => null !== $blueUser && null !== $blueUser->getAvatarPath() 'blueAvatar' => null !== $blueUser && null !== $blueUser->avatarPath
? $this->cacheManager->generateUrl($blueUser->getAvatarPath(), 'avatar_thumb') ? $this->cacheManager->generateUrl($blueUser->avatarPath, 'avatar_thumb')
: null, : null,
]; ];
} }