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

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

View File

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

View File

@@ -78,22 +78,22 @@ class ProfileController extends AbstractController
$since = new DateTime('first day of -5 months midnight');
$recentGames = $this->repo->findFinishedForUserSince($user, $since);
$userId = $user->getId();
$userId = $user->id;
foreach ($recentGames as $game) {
if (!$game->getUpdated()) {
if (!$game->updated) {
continue;
}
$month = $game->getUpdated()->format('Y-m');
$month = $game->updated->format('Y-m');
if (!isset($monthlyData[$month])) {
continue;
}
$isRed = $game->getRed()?->getId() === $userId;
$myPts = $isRed ? $game->getRedPoints() : $game->getBluePoints();
$oppPts = $isRed ? $game->getBluePoints() : $game->getRedPoints();
$resign = $game->getResign();
$isRed = $game->red?->id === $userId;
$myPts = $isRed ? $game->redPoints : $game->bluePoints;
$oppPts = $isRed ? $game->bluePoints : $game->redPoints;
$resign = $game->resign;
$myColor = $isRed ? 'red' : 'blue';
$oppColor = $isRed ? 'blue' : 'red';
@@ -131,12 +131,12 @@ class ProfileController extends AbstractController
],
'recent' => ($recent = $this->repo->findRecentFinishedForUser($user, 30)),
'gamesData' => array_map(function (PlayedGame $game) use ($userId, $cacheManager): array {
$isRed = $game->getRed()?->getId() === $userId;
$resign = $game->getResign();
$isRed = $game->red?->id === $userId;
$resign = $game->resign;
$myColor = $isRed ? 'red' : 'blue';
$oppColor = $isRed ? 'blue' : 'red';
$myPts = $isRed ? $game->getRedPoints() : $game->getBluePoints();
$oppPts = $isRed ? $game->getBluePoints() : $game->getRedPoints();
$myPts = $isRed ? $game->redPoints : $game->bluePoints;
$oppPts = $isRed ? $game->bluePoints : $game->redPoints;
$result = 'draw';
if ($resign === $myColor) $result = 'loss';
@@ -146,33 +146,33 @@ class ProfileController extends AbstractController
elseif ($myPts < $oppPts) $result = 'loss';
}
$redAvatarPath = $game->getRed()?->getAvatarPath();
$blueAvatarPath = $game->getBlue()?->getAvatarPath();
$redAvatarPath = $game->red?->avatarPath;
$blueAvatarPath = $game->blue?->avatarPath;
return [
'id' => $game->getId(),
'uuid' => $game->getUuid()?->toRfc4122(),
'id' => $game->id,
'uuid' => $game->uuid?->toRfc4122(),
'redName' =>
$game->getRed()?->getUsername() ?? $game->getRedAnon()?->getUserName() ?? 'Guest',
$game->red?->getUsername() ?? $game->redAnon?->userName ?? 'Guest',
'blueName' =>
$game->getBlue()?->getUsername() ?? $game->getBlueAnon()?->getUserName() ?? 'Guest',
$game->blue?->getUsername() ?? $game->blueAnon?->userName ?? 'Guest',
'redAvatar' => $redAvatarPath ? $cacheManager->generateUrl($redAvatarPath, 'avatar_thumb') : null,
'blueAvatar' => $blueAvatarPath ? $cacheManager->generateUrl($blueAvatarPath, 'avatar_thumb') : null,
'redPoints' => $game->getRedPoints(),
'bluePoints' => $game->getBluePoints(),
'redExplodedBomb' => $game->getRedExplodedBomb(),
'blueExplodedBomb' => $game->getBlueExplodedBomb(),
'redPoints' => $game->redPoints,
'bluePoints' => $game->bluePoints,
'redExplodedBomb' => $game->redExplodedBomb,
'blueExplodedBomb' => $game->blueExplodedBomb,
'resign' => $resign,
'created' => $game->getCreated()?->format('Y-m-d H:i'),
'date' => $game->getUpdated()?->format('Y-m-d H:i'),
'created' => $game->created?->format('Y-m-d H:i'),
'date' => $game->updated?->format('Y-m-d H:i'),
'isRed' => $isRed,
'result' => $result,
'myPoints' => $myPts,
'oppPoints' => $oppPts,
'redBonusPoints' => $game->getRedBonusPoints() ?? 0,
'blueBonusPoints' => $game->getBlueBonusPoints() ?? 0,
'redBonusStats' => $game->getRedBonusStats() ?? [],
'blueBonusStats' => $game->getBlueBonusStats() ?? [],
'redBonusPoints' => $game->redBonusPoints ?? 0,
'blueBonusPoints' => $game->blueBonusPoints ?? 0,
'redBonusStats' => $game->redBonusStats ?? [],
'blueBonusStats' => $game->blueBonusStats ?? [],
];
}, $recent),
'chartData' => [
@@ -202,10 +202,10 @@ class ProfileController extends AbstractController
$mines = [];
$bonus = [];
foreach ($recent as $i => $game) {
$isRed = $game->getRed()?->getId() === $userId;
$isRed = $game->red?->id === $userId;
$labels[] = '#' . ($i + 1);
$mines[] = (int)($isRed ? $game->getRedPoints() : $game->getBluePoints());
$bonus[] = (float)($isRed ? $game->getRedBonusPoints() : $game->getBlueBonusPoints()) ?: 0;
$mines[] = (int)($isRed ? $game->redPoints : $game->bluePoints);
$bonus[] = (float)($isRed ? $game->redBonusPoints : $game->blueBonusPoints) ?: 0;
}
return ['labels' => $labels, 'mines' => $mines, 'bonus' => $bonus];
@@ -224,17 +224,17 @@ class ProfileController extends AbstractController
throw $this->createNotFoundException('Battle not found.');
}
$redName = $game->getRed()?->getUsername() ?? ($game->getRedAnon() !== null ? 'Anonymous' : 'Guest');
$blueName = $game->getBlue()?->getUsername() ?? ($game->getBlueAnon() !== null ? 'Anonymous' : 'Guest');
$redPts = $game->getRedPoints();
$bluePts = $game->getBluePoints();
$resign = $game->getResign();
$redAvatar = $game->getRed()?->getAvatarPath();
$blueAvatar = $game->getBlue()?->getAvatarPath();
$redBonusPoints = $game->getRedBonusPoints() ?? 0;
$blueBonusPoints = $game->getBlueBonusPoints() ?? 0;
$redBonusStats = $game->getRedBonusStats() ?? [];
$blueBonusStats = $game->getBlueBonusStats() ?? [];
$redName = $game->red?->getUsername() ?? ($game->redAnon !== null ? 'Anonymous' : 'Guest');
$blueName = $game->blue?->getUsername() ?? ($game->blueAnon !== null ? 'Anonymous' : 'Guest');
$redPts = $game->redPoints;
$bluePts = $game->bluePoints;
$resign = $game->resign;
$redAvatar = $game->red?->avatarPath;
$blueAvatar = $game->blue?->avatarPath;
$redBonusPoints = $game->redBonusPoints ?? 0;
$blueBonusPoints = $game->blueBonusPoints ?? 0;
$redBonusStats = $game->redBonusStats ?? [];
$blueBonusStats = $game->blueBonusStats ?? [];
if ($resign === 'red') {
$summary = "$redName resigned — $blueName wins";
@@ -321,7 +321,7 @@ class ProfileController extends AbstractController
$ext = $file->guessExtension() ?? 'jpg';
$newPath = sprintf('avatar/%s.%s', Uuid::v4()->toRfc4122(), $ext);
$oldPath = $user->getAvatarPath();
$oldPath = $user->avatarPath;
/** Remove old file and any cached thumbnails */
if ($oldPath) {
@@ -343,7 +343,7 @@ class ProfileController extends AbstractController
}
fclose($stream);
$user->setAvatarPath($newPath);
$user->avatarPath = $newPath;
$em->flush();
return $this->json([
@@ -360,18 +360,18 @@ class ProfileController extends AbstractController
$credentials = $this->webAuthnService->getCredentialsForUser($user);
$credentialsData = array_map(fn($cred) => [
'id' => $cred->getId(),
'credentialName' => $cred->getCredentialName(),
'createdAt' => $cred->getCreatedAt()?->format('Y-m-d H:i:s'),
'lastUsedAt' => $cred->getLastUsedAt()?->format('Y-m-d H:i:s'),
'isBackupEligible' => $cred->isBackupEligible(),
'isBackupAuthenticated' => $cred->isBackupAuthenticated(),
'id' => $cred->id,
'credentialName' => $cred->credentialName,
'createdAt' => $cred->createdAt?->format('Y-m-d H:i:s'),
'lastUsedAt' => $cred->lastUsedAt?->format('Y-m-d H:i:s'),
'isBackupEligible' => $cred->isBackupEligible,
'isBackupAuthenticated' => $cred->isBackupAuthenticated,
], $credentials);
return $this->render('Security/profile_security.html.twig', [
'credentials' => $credentialsData,
'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()) {
$token = bin2hex(random_bytes(32));
$user
->setIsVerified(false)
->setVerificationToken($token)
->setPassword($hasher->hashPassword($user, $form->get('plainPassword')->getData()));
$user->isVerified = false;
$user->verificationToken = $token;
$user->password = $hasher->hashPassword($user, $form->get('plainPassword')->getData());
$em->persist($user);
$em->flush();
@@ -108,7 +107,7 @@ class SecurityController extends AbstractController
$mailer->send(
new TemplatedEmail()
->from('noreply@mineseeker.hu')
->to($user->getEmail())
->to($user->email)
->subject('Activate your MineSeeker account')
->htmlTemplate('emails/activation.html.twig')
->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');
}
@@ -156,11 +155,10 @@ class SecurityController extends AbstractController
$email = $form->get('email')->getData();
$user = $userRepository->findOneByEmail($email);
if ($user && $user->isVerified()) {
if ($user && $user->isVerified) {
$token = bin2hex(random_bytes(32));
$user
->setResetToken($token)
->setResetTokenExpiresAt(new DateTime('+1 hour'));
$user->resetToken = $token;
$user->resetTokenExpiresAt = new DateTime('+1 hour');
$em->flush();
$resetUrl = $this->generateUrl(
@@ -206,7 +204,7 @@ class SecurityController extends AbstractController
): Response {
$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.');
return $this->redirectToRoute('MineSeekerBundle_forgot_password');
}
@@ -215,10 +213,9 @@ class SecurityController extends AbstractController
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user
->setPassword($hasher->hashPassword($user, $form->get('plainPassword')->getData()))
->setResetToken(null)
->setResetTokenExpiresAt(null);
$user->password = $hasher->hashPassword($user, $form->get('plainPassword')->getData());
$user->resetToken = null;
$user->resetTokenExpiresAt = null;
$em->flush();
$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');
}
$user->setIsVerified(true)->setVerificationToken(null);
$user->isVerified = true;
$user->verificationToken = null;
$em->flush();
/** Send admin notification about account activation */

View File

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

View File

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

View File

@@ -34,95 +34,29 @@ use Doctrine\ORM\Mapping\Table;
class ContactMessage
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
public private(set) ?int $id = null;
#[Column]
private string $name;
public string $name;
#[Column]
private string $email;
public string $email;
#[Column(type: Types::TEXT)]
private string $content;
public string $content;
#[Column]
private bool $consent = false;
public bool $consent = false;
#[Column]
private DateTimeImmutable $createdAt;
public private(set) DateTimeImmutable $createdAt;
#[Column(length: 45, nullable: true)]
private ?string $ipAddress = null;
public ?string $ipAddress = null;
public function __construct()
{
$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
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
public private(set) ?int $id = null;
#[Column(length: 100)]
private ?string $userName = null;
public ?string $userName = null;
#[Column(length: 20, nullable: true)]
private ?string $ip = null;
public ?string $ip = null;
#[Column(length: 100, nullable: true)]
private ?string $country = null;
public ?string $country = null;
#[Column(nullable: true)]
private ?string $userAgent = null;
public ?string $userAgent = null;
#[Column(type: Types::DATETIME_MUTABLE)]
private ?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;
}
public ?DateTime $connTimestamp = null;
}

View File

@@ -35,14 +35,14 @@ use Doctrine\ORM\Mapping\OneToOne;
class Grid
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
public private(set) ?int $id = null;
#[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')]
private Collection $gridRow;
public private(set) Collection $gridRow;
public function __construct()
@@ -50,30 +50,10 @@ class Grid
$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
{
$this->gridRow->add($gridRow);
$gridRow->setGrid($this);
$gridRow->grid = $this;
}
public function removeGridRow(GridRow $gridRow): void

View File

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

View File

@@ -39,68 +39,68 @@ use Symfony\Component\Uid\Uuid;
class PlayedGame
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
public private(set) ?int $id = null;
#[Column(type: 'uuid', unique: true)]
private ?Uuid $uuid = null;
public ?Uuid $uuid = null;
#[Column(length: 50)]
private ?string $gameAssoc = null;
public ?string $gameAssoc = null;
#[Column(length: 5, nullable: true)]
private ?int $redPoints = null;
public ?int $redPoints = null;
#[Column(length: 5, nullable: true)]
private ?int $bluePoints = null;
public ?int $bluePoints = null;
#[Column(nullable: true)]
private ?bool $redExplodedBomb = null;
public ?bool $redExplodedBomb = null;
#[Column(nullable: true)]
private ?bool $blueExplodedBomb = null;
public ?bool $blueExplodedBomb = null;
#[Column(length: 7, nullable: true)]
private ?string $resign = null;
public ?string $resign = null;
#[Column(nullable: true)]
private ?float $redBonusPoints = null;
public ?float $redBonusPoints = null;
#[Column(nullable: true)]
private ?float $blueBonusPoints = null;
public ?float $blueBonusPoints = null;
#[Column(nullable: true)]
private ?array $redBonusStats = null;
public ?array $redBonusStats = null;
#[Column(nullable: true)]
private ?array $blueBonusStats = null;
public ?array $blueBonusStats = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $created = null;
public ?DateTime $created = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $updated = null;
public ?DateTime $updated = null;
#[OneToOne(mappedBy: 'playedGame', cascade: ['persist'])]
private ?Grid $grid = null;
public ?Grid $grid = null;
#[ManyToOne]
#[JoinColumn(name: 'red_id', referencedColumnName: 'id', nullable: true)]
private ?User $red = null;
public ?User $red = null;
#[ManyToOne]
#[JoinColumn(name: 'red_anon', referencedColumnName: 'id', nullable: true)]
private ?Gamer $redAnon = null;
public ?Gamer $redAnon = null;
#[ManyToOne]
#[JoinColumn(name: 'blue_id', referencedColumnName: 'id', nullable: true)]
private ?User $blue = null;
public ?User $blue = null;
#[ManyToOne]
#[JoinColumn(name: 'blue_anon', referencedColumnName: 'id', nullable: true)]
private ?Gamer $blueAnon = null;
public ?Gamer $blueAnon = null;
#[OneToMany(mappedBy: 'playedGame', targetEntity: Step::class)]
private Collection $steps;
public private(set) Collection $steps;
public function __construct()
@@ -108,196 +108,4 @@ class PlayedGame
$this->steps = new ArrayCollection();
$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);
/*
/**
* This file is part of the SplendidBear Websites' projects.
*
* Copyright (c) 2026 @ www.splendidbear.org
@@ -33,102 +33,26 @@ use Doctrine\ORM\Mapping\ManyToOne;
class Step
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
public private(set) ?int $id = null;
#[Column(length: 3)]
private ?int $row = null;
public ?int $row = null;
#[Column(length: 3)]
private ?int $col = null;
public ?int $col = null;
#[Column(nullable: true)]
private ?bool $wBomb = null;
public ?bool $wBomb = null;
#[Column(length: 10, nullable: true)]
private ?string $player = null;
public ?string $player = null;
#[Column(type: Types::JSON, nullable: true)]
private ?array $revealedCells = null;
public ?array $revealedCells = null;
#[ManyToOne(inversedBy: 'steps')]
private ?PlayedGame $playedGame = null;
public ?PlayedGame $playedGame = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?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;
}
public ?DateTime $created = null;
}

View File

@@ -43,61 +43,53 @@ use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwoFactorInterface, BackupCodeInterface
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
public private(set) ?int $id = null;
#[Column(length: 180, unique: true)]
private ?string $username = null;
public ?string $username = null;
#[Column]
private array $roles = [];
public array $roles = [];
#[Column(nullable: true)]
private ?string $password = null;
public ?string $password = null;
#[Column(length: 254, unique: true, nullable: true)]
private ?string $email = null;
public ?string $email = null;
#[Column]
private bool $isVerified = false;
public bool $isVerified = false;
#[Column(length: 64, nullable: true)]
private ?string $verificationToken = null;
public ?string $verificationToken = null;
#[Column(length: 64, nullable: true)]
private ?string $resetToken = null;
public ?string $resetToken = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $resetTokenExpiresAt = null;
public ?DateTime $resetTokenExpiresAt = null;
#[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)]
private ?array $backupCodes = [];
public ?array $backupCodes = [] {
get => $this->backupCodes ?? [];
}
#[Column(length: 255, nullable: true)]
private ?string $avatarPath = null;
public ?string $avatarPath = null;
#[Column(nullable: true)]
private ?bool $consentGiven = null;
public ?bool $consentGiven = null;
public function getId(): ?int
{
return $this->id;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getUserIdentifier(): string
{
return (string)$this->username;
@@ -105,15 +97,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwo
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
return array_unique([...$this->roles, 'ROLE_USER']);
}
public function getPassword(): ?string
@@ -121,82 +105,10 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwo
return $this->password;
}
public function setPassword(?string $password): self
{
$this->password = $password;
return $this;
}
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
{
return null !== $this->totpSecret;
@@ -215,46 +127,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwo
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
{
return \in_array($code, $this->backupCodes ?? [], true);
return \in_array($code, $this->backupCodes, true);
}
public function invalidateBackupCode(string $code): void
{
$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;
$this->backupCodes = array_values(array_filter($this->backupCodes, fn($c) => $c !== $code));
}
}

View File

@@ -20,6 +20,8 @@ use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\Table;
use JsonException;
use RuntimeException;
/**
* Class WebAuthnCredential
@@ -36,29 +38,29 @@ use Doctrine\ORM\Mapping\Table;
class WebAuthnCredential
{
#[Id, GeneratedValue, Column]
private ?int $id = null;
public private(set) ?int $id = null;
#[ManyToOne(targetEntity: User::class)]
#[JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?User $user = null;
public ?User $user = null;
#[Column(type: Types::TEXT)]
private ?string $credentialData = null;
public ?string $credentialData = null;
#[Column(length: 255)]
private ?string $credentialName = null;
public ?string $credentialName = null;
#[Column(type: Types::DATETIME_MUTABLE)]
private ?DateTime $createdAt = null;
public ?DateTime $createdAt = null;
#[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?DateTime $lastUsedAt = null;
public ?DateTime $lastUsedAt = null;
#[Column]
private bool $isBackupEligible = false;
public bool $isBackupEligible = false;
#[Column]
private bool $isBackupAuthenticated = false;
public bool $isBackupAuthenticated = false;
public function __construct()
@@ -66,106 +68,27 @@ class WebAuthnCredential
$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()
{
// Return the raw credential data (JSON decoded)
if ($this->credentialData === 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
{
// Handle both array and object input
if (is_array($source)) {
$this->credentialData = json_encode($source);
} else {
$this->credentialData = (string)$source;
try {
$this->credentialData = is_array($source) ? json_encode($source, JSON_THROW_ON_ERROR) : (string)$source;
} catch (JsonException $e) {
throw new RuntimeException("Unable to encode JSON: {$e->getMessage()}");
}
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
FROM played_game g
WHERE (g.red_id = :uid OR g.blue_id = :uid)',
['uid' => $user->getId()],
['uid' => $user->id],
)->fetchAssociative();
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)
OR (g.blue_id = :uid AND g.blue_points IS NOT NULL)
)',
['uid' => $user->getId()],
['uid' => $user->id],
)->fetchAssociative();
if (!$result || (int) $result['total_games'] === 0) {
@@ -306,7 +306,7 @@ class PlayedGameRepository extends ServiceEntityRepository
*/
public function findBonusStatsForUser(User $user): array
{
$userId = $user->getId();
$userId = $user->id;
$qb = $this->createQueryBuilder('g');
$qb->where($qb->expr()->orX(
$qb->expr()->eq('g.red', ':u'),
@@ -323,10 +323,10 @@ class PlayedGameRepository extends ServiceEntityRepository
$gameCount = 0;
foreach ($games as $game) {
$isRed = $game->getRed()?->getId() === $userId;
$totalBonusPoints += (float) (($isRed ? $game->getRedBonusPoints() : $game->getBlueBonusPoints()) ?? 0.0);
$isRed = $game->red?->id === $userId;
$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));
$totalBlindHits += (int) ($stats['blindHits'] ?? 0);
$totalEdgeMines += (int) ($stats['edgeMines'] ?? 0);

View File

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

View File

@@ -16,6 +16,15 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
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>
*
* @method WebAuthnCredential|null find($id, $lockMode = null, $lockVersion = null)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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