Compare commits
2 Commits
v2026.2.1-
...
v2026.2.1-
| Author | SHA1 | Date | |
|---|---|---|---|
| 42c552c528 | |||
| 3b376e5386 |
@@ -1,6 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.1-7 (2026-04-16)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Add consent checkbox to user's registration - and fix the sharing pics #4. [Lang]
|
||||||
|
|
||||||
|
* Add correct version numbering and CHANGELOG - and add the LICENSE #4. [Lang]
|
||||||
|
|
||||||
|
|
||||||
## v2026.2.1-6 (2026-04-16)
|
## v2026.2.1-6 (2026-04-16)
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ services:
|
|||||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
MINIO_ENDPOINT: http://minio:9000
|
MINIO_ENDPOINT: http://minio:9000
|
||||||
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9000}
|
MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL:-http://localhost:9000}
|
||||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES}
|
# IMPORTANT: Set TRUSTED_PROXIES to your reverse proxy IP in production.
|
||||||
|
# For Docker on same host, use: 172.17.0.1 (default bridge) or 172.16.0.0/12 (overlay network)
|
||||||
|
# For Kubernetes or external proxy, use the proxy's IP address.
|
||||||
|
# WARNING: Using 0.0.0.0/0 is insecure in production environments!
|
||||||
|
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1}
|
||||||
volumes:
|
volumes:
|
||||||
- app_var:/app/var
|
- app_var:/app/var
|
||||||
- caddy_data:/data
|
- caddy_data:/data
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ security:
|
|||||||
auth_code_parameter_name: _auth_code
|
auth_code_parameter_name: _auth_code
|
||||||
post_only: true
|
post_only: true
|
||||||
default_target_path: MineSeekerBundle_homepage
|
default_target_path: MineSeekerBundle_homepage
|
||||||
|
always_use_default_target_path: false
|
||||||
prepare_on_login: true
|
prepare_on_login: true
|
||||||
prepare_on_access_denied: true
|
prepare_on_access_denied: true
|
||||||
form_login:
|
form_login:
|
||||||
login_path: MineSeekerBundle_login
|
login_path: MineSeekerBundle_login
|
||||||
check_path: MineSeekerBundle_login
|
check_path: MineSeekerBundle_login
|
||||||
default_target_path: MineSeekerBundle_homepage
|
default_target_path: MineSeekerBundle_homepage
|
||||||
|
always_use_default_target_path: false
|
||||||
username_parameter: _username
|
username_parameter: _username
|
||||||
password_parameter: _password
|
password_parameter: _password
|
||||||
enable_csrf: true
|
enable_csrf: true
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class MercureController extends AbstractController
|
|||||||
#[Route('/api/game/join/{gameAssoc}', name: 'MineSeekerBundle_api_game_join', methods: ['POST'])]
|
#[Route('/api/game/join/{gameAssoc}', name: 'MineSeekerBundle_api_game_join', methods: ['POST'])]
|
||||||
public function join(string $gameAssoc, Request $request): JsonResponse
|
public function join(string $gameAssoc, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$this->topicManager->subscribe($gameAssoc, $this->resolveUserName($request), $this->getUser());
|
$this->topicManager->subscribe($gameAssoc, $this->resolveUserName($request), $this->getUser(), $request);
|
||||||
|
|
||||||
return $this->json(['success' => true]);
|
return $this->json(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use App\Form\ResetPasswordFormType;
|
|||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use LogicException;
|
||||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
@@ -64,7 +65,7 @@ class SecurityController extends AbstractController
|
|||||||
#[Route('/logout', name: 'MineSeekerBundle_logout', methods: ['POST'])]
|
#[Route('/logout', name: 'MineSeekerBundle_logout', methods: ['POST'])]
|
||||||
public function logout(): never
|
public function logout(): never
|
||||||
{
|
{
|
||||||
throw new \LogicException('This action is intercepted by the security firewall.');
|
throw new LogicException('This action is intercepted by the security firewall.');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/register', name: 'MineSeekerBundle_register')]
|
#[Route('/register', name: 'MineSeekerBundle_register')]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
namespace App\Interfaces;
|
namespace App\Interfaces;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +25,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
|||||||
*/
|
*/
|
||||||
interface TopicManagerInterface
|
interface TopicManagerInterface
|
||||||
{
|
{
|
||||||
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user): void;
|
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user, Request $request): void;
|
||||||
|
|
||||||
public function unSubscribe(string $gameAssoc, string $userName): void;
|
public function unSubscribe(string $gameAssoc, string $userName): void;
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,15 @@ use App\Entity\User;
|
|||||||
use App\Interfaces\TopicManagerInterface;
|
use App\Interfaces\TopicManagerInterface;
|
||||||
use App\Repository\PlayedGameRepository;
|
use App\Repository\PlayedGameRepository;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
|
|
||||||
use DateTimeInterface;
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use DateTimeInterface;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use JsonException;
|
use JsonException;
|
||||||
|
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Mercure\HubInterface;
|
use Symfony\Component\Mercure\HubInterface;
|
||||||
use Symfony\Component\Mercure\Update;
|
use Symfony\Component\Mercure\Update;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
@@ -52,7 +53,7 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user): void
|
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user, Request $request): void
|
||||||
{
|
{
|
||||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||||
if (null === $playedGame) {
|
if (null === $playedGame) {
|
||||||
@@ -70,7 +71,7 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
|
|
||||||
/** Save the player to the database on a fresh join */
|
/** Save the player to the database on a fresh join */
|
||||||
if (!$isKnown && $count < 2) {
|
if (!$isKnown && $count < 2) {
|
||||||
$users = $this->saveUserToDb($gameAssoc, $userName, $user, $count + 1);
|
$users = $this->saveUserToDb($gameAssoc, $userName, $user, $count + 1, $request);
|
||||||
$count = $this->getPlayerCount($users);
|
$count = $this->getPlayerCount($users);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,9 +169,6 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
// Normal move
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
$coords = $event['coords'];
|
$coords = $event['coords'];
|
||||||
$player = $event['player']; // 'red' | 'blue'
|
$player = $event['player']; // 'red' | 'blue'
|
||||||
$isBomb = (bool)$event['bomb'];
|
$isBomb = (bool)$event['bomb'];
|
||||||
@@ -243,10 +241,6 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
// Grid helpers
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
|
|
||||||
/** Load the grid rows from the database as a 2-D array. */
|
/** Load the grid rows from the database as a 2-D array. */
|
||||||
private function loadGrid(string $gameAssoc): array
|
private function loadGrid(string $gameAssoc): array
|
||||||
{
|
{
|
||||||
@@ -403,10 +397,6 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
return $mines;
|
return $mines;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
// Database helpers
|
|
||||||
// ------------------------------------------------------------------ //
|
|
||||||
|
|
||||||
private function getPlayedGame(string $gameAssoc): ?PlayedGame
|
private function getPlayedGame(string $gameAssoc): ?PlayedGame
|
||||||
{
|
{
|
||||||
return $this->playedGameRepository->findOneByGameAssoc($gameAssoc);
|
return $this->playedGameRepository->findOneByGameAssoc($gameAssoc);
|
||||||
@@ -462,13 +452,18 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function saveUserToDb(string $gameAssoc, string $userName, ?UserInterface $user, int $count): array
|
private function saveUserToDb(
|
||||||
{
|
string $gameAssoc,
|
||||||
|
string $userName,
|
||||||
|
?UserInterface $user,
|
||||||
|
int $count,
|
||||||
|
Request $request
|
||||||
|
): array {
|
||||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||||
|
|
||||||
null !== $user
|
null !== $user
|
||||||
? $this->saveRegisteredUser($userName, $count, $playedGame)
|
? $this->saveRegisteredUser($userName, $count, $playedGame)
|
||||||
: $this->saveAnonUser($userName, $count, $playedGame);
|
: $this->saveAnonUser($userName, $count, $playedGame, $request);
|
||||||
|
|
||||||
$this->entityManager->persist($playedGame);
|
$this->entityManager->persist($playedGame);
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
@@ -495,11 +490,14 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame): void
|
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame, Request $request): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$anon = new Gamer();
|
$anon = new Gamer();
|
||||||
$anon->setUsername($userName);
|
$anon->setUserName($userName);
|
||||||
|
$anon->setIp($request->getClientIp());
|
||||||
|
$anon->setCountry($this->extractCountry($request));
|
||||||
|
$anon->setUserAgent($request->headers->get('User-Agent'));
|
||||||
$anon->setConnTimestamp(new DateTime());
|
$anon->setConnTimestamp(new DateTime());
|
||||||
$this->entityManager->persist($anon);
|
$this->entityManager->persist($anon);
|
||||||
|
|
||||||
@@ -585,4 +583,27 @@ readonly class TopicManager implements TopicManagerInterface
|
|||||||
$this->logger->error('Lobby publish error: ' . $e->getMessage());
|
$this->logger->error('Lobby publish error: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function extractCountry(Request $request): ?string
|
||||||
|
{
|
||||||
|
/** Common headers used by CDNs and proxies to pass country information */
|
||||||
|
$countryHeaders = [
|
||||||
|
'CF-IPCountry', // Cloudflare
|
||||||
|
'CloudFront-Viewer-Country', // AWS CloudFront
|
||||||
|
'X-Country-Code', // Custom header
|
||||||
|
'X-Geoip-Country', // Generic GeoIP header
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($countryHeaders as $header) {
|
||||||
|
$country = $request->headers->get($header);
|
||||||
|
|
||||||
|
if (empty($country)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr($country, 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user