diff --git a/src/Game/Controller/GameAdminController.php b/src/Game/Controller/GameAdminController.php
index aaac8ec..763f671 100644
--- a/src/Game/Controller/GameAdminController.php
+++ b/src/Game/Controller/GameAdminController.php
@@ -1,9 +1,12 @@
findByRole('ROLE_PLAYER');
- $sessions = $sessionRepository->findAll();
+ $allUsers = $userRepository->findAll();
+ $allSessions = $sessionRepository->findAll();
+ $activeSessions = array_filter(
+ $allSessions,
+ fn(Session $s) => in_array($s->getStatus(), [SessionStatus::CREATED, SessionStatus::READY, SessionStatus::PLAYING])
+ );
return $this->render('game/admin/index.html.twig', [
- 'players' => $players,
- 'sessions' => $sessions,
+ 'totalUsers' => count($allUsers),
+ 'totalPlayers' => count($userRepository->findByRole('ROLE_PLAYER')),
+ 'totalAdmins' => count($userRepository->findByRole('ROLE_ADMIN')),
+ 'totalGames' => count($gameRepository->findAll()),
+ 'totalSessions' => count($allSessions),
+ 'activeSessions' => count($activeSessions),
]);
}
@@ -44,18 +56,13 @@ final class GameAdminController extends AbstractController
$username = $player->getUser()->getUsername();
$logFile = $this->projectDir . '/var/log/sessions/' . $session->getId() . '/' . $username . '.txt';
- $logs = '';
- if (file_exists($logFile)) {
- $logs = file_get_contents($logFile);
- }
-
$playersLogs[] = [
'username' => $username,
- 'logs' => $logs,
+ 'logs' => file_exists($logFile) ? file_get_contents($logFile) : '',
];
}
- return $this->render('game/admin/session.html.twig', [
+ return $this->render('game/admin/sessions/view.html.twig', [
'session' => $session,
'playersLogs' => $playersLogs,
]);
diff --git a/src/Game/Controller/GameAdminEmailLogController.php b/src/Game/Controller/GameAdminEmailLogController.php
new file mode 100644
index 0000000..a634ef6
--- /dev/null
+++ b/src/Game/Controller/GameAdminEmailLogController.php
@@ -0,0 +1,24 @@
+render('game/admin/email_log/index.html.twig', [
+ 'logs' => $emailLogRepository->findBy([], ['sentAt' => 'DESC']),
+ ]);
+ }
+}
diff --git a/src/Game/Controller/GameAdminGameController.php b/src/Game/Controller/GameAdminGameController.php
new file mode 100644
index 0000000..55a5df3
--- /dev/null
+++ b/src/Game/Controller/GameAdminGameController.php
@@ -0,0 +1,70 @@
+render('game/admin/games/index.html.twig', [
+ 'games' => $gameRepository->findBy([], ['id' => 'ASC']),
+ ]);
+ }
+
+ #[Route('/{id}/edit', name: 'game_admin_game_edit', methods: ['GET', 'POST'])]
+ public function edit(
+ Game $game,
+ Request $request,
+ EntityManagerInterface $em,
+ GameSettingRepository $gameSettingRepository,
+ ): Response {
+ $totalTimeSetting = $gameSettingRepository->getSetting($game, GameSettingType::TOTAL_TIME);
+
+ $form = $this->createForm(AdminGameType::class, $game, [
+ 'total_time' => $totalTimeSetting?->getValue(),
+ ]);
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $totalTime = $form->get('totalTime')->getData();
+
+ if ($totalTime !== null) {
+ if (!$totalTimeSetting) {
+ $totalTimeSetting = new GameSetting();
+ $totalTimeSetting->setGame($game);
+ $totalTimeSetting->setName(GameSettingType::TOTAL_TIME);
+ }
+ $totalTimeSetting->setValue((string) $totalTime);
+ $em->persist($totalTimeSetting);
+ }
+
+ $em->flush();
+ $this->addFlash('success', sprintf('Game "%s" updated.', $game->getName()));
+
+ return $this->redirectToRoute('game_admin_games');
+ }
+
+ return $this->render('game/admin/games/edit.html.twig', [
+ 'game' => $game,
+ 'form' => $form,
+ ]);
+ }
+}
diff --git a/src/Game/Controller/GameAdminSessionController.php b/src/Game/Controller/GameAdminSessionController.php
new file mode 100644
index 0000000..75cfee9
--- /dev/null
+++ b/src/Game/Controller/GameAdminSessionController.php
@@ -0,0 +1,44 @@
+render('game/admin/sessions/index.html.twig', [
+ 'sessions' => $sessionRepository->findBy([], ['created' => 'DESC']),
+ ]);
+ }
+
+ #[Route('/{id}/close', name: 'game_admin_session_close', methods: ['POST'])]
+ public function close(Session $session, Request $request, EntityManagerInterface $em): Response
+ {
+ if (!$this->isCsrfTokenValid('close_session_' . $session->getId(), $request->request->get('_token'))) {
+ $this->addFlash('danger', 'Invalid CSRF token.');
+ return $this->redirectToRoute('game_admin_sessions');
+ }
+
+ $session->setStatus(SessionStatus::LOST);
+ $em->flush();
+
+ $this->addFlash('success', sprintf('Session #%d closed.', $session->getId()));
+
+ return $this->redirectToRoute('game_admin_sessions');
+ }
+}
diff --git a/src/Game/Controller/GameAdminUserController.php b/src/Game/Controller/GameAdminUserController.php
new file mode 100644
index 0000000..1030149
--- /dev/null
+++ b/src/Game/Controller/GameAdminUserController.php
@@ -0,0 +1,74 @@
+render('game/admin/users/index.html.twig', [
+ 'users' => $userRepository->findBy([], ['id' => 'ASC']),
+ ]);
+ }
+
+ #[Route('/{id}/edit', name: 'game_admin_user_edit', methods: ['GET', 'POST'])]
+ public function edit(
+ User $user,
+ Request $request,
+ EntityManagerInterface $em,
+ UserPasswordHasherInterface $passwordHasher,
+ ): Response {
+ $form = $this->createForm(AdminUserType::class, $user);
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $plainPassword = $form->get('plainPassword')->getData();
+ if ($plainPassword) {
+ $user->setPassword($passwordHasher->hashPassword($user, $plainPassword));
+ }
+
+ $em->flush();
+ $this->addFlash('success', sprintf('User "%s" updated.', $user->getUsername()));
+
+ return $this->redirectToRoute('game_admin_users');
+ }
+
+ return $this->render('game/admin/users/edit.html.twig', [
+ 'user' => $user,
+ 'form' => $form,
+ ]);
+ }
+
+ #[Route('/{id}/delete', name: 'game_admin_user_delete', methods: ['POST'])]
+ public function delete(User $user, Request $request, EntityManagerInterface $em): Response
+ {
+ if (!$this->isCsrfTokenValid('delete_user_' . $user->getId(), $request->request->get('_token'))) {
+ $this->addFlash('danger', 'Invalid CSRF token.');
+ return $this->redirectToRoute('game_admin_users');
+ }
+
+ $username = $user->getUsername();
+ $em->remove($user);
+ $em->flush();
+
+ $this->addFlash('success', sprintf('User "%s" deleted.', $username));
+
+ return $this->redirectToRoute('game_admin_users');
+ }
+}
diff --git a/src/Game/Form/AdminGameType.php b/src/Game/Form/AdminGameType.php
new file mode 100644
index 0000000..3512d98
--- /dev/null
+++ b/src/Game/Form/AdminGameType.php
@@ -0,0 +1,55 @@
+add('name', TextType::class, [
+ 'constraints' => [new NotBlank()],
+ ])
+ ->add('numberOfPlayers', IntegerType::class, [
+ 'label' => 'Number of players',
+ 'constraints' => [new NotBlank(), new Positive()],
+ ])
+ ->add('status', ChoiceType::class, [
+ 'choices' => [
+ 'In development' => GameStatus::IN_DEVELOPMENT,
+ 'Locked' => GameStatus::LOCKED,
+ 'Open' => GameStatus::OPEN,
+ ],
+ ])
+ ->add('totalTime', IntegerType::class, [
+ 'mapped' => false,
+ 'required' => false,
+ 'label' => 'Total time (seconds)',
+ 'data' => $options['total_time'],
+ 'attr' => ['placeholder' => 'e.g. 3600 for 1 hour'],
+ ]);
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'data_class' => Game::class,
+ 'total_time' => null,
+ ]);
+
+ $resolver->setAllowedTypes('total_time', ['null', 'string', 'int']);
+ }
+}
diff --git a/src/Tech/Form/AdminUserType.php b/src/Tech/Form/AdminUserType.php
new file mode 100644
index 0000000..d701d87
--- /dev/null
+++ b/src/Tech/Form/AdminUserType.php
@@ -0,0 +1,58 @@
+add('email', EmailType::class, [
+ 'constraints' => [new NotBlank(), new Email()],
+ ])
+ ->add('username', TextType::class, [
+ 'constraints' => [new NotBlank(), new Length(min: 2, max: 180)],
+ ])
+ ->add('plainPassword', PasswordType::class, [
+ 'mapped' => false,
+ 'required' => false,
+ 'label' => 'New password',
+ 'attr' => ['autocomplete' => 'new-password', 'placeholder' => 'Leave blank to keep current'],
+ ])
+ ->add('roles', ChoiceType::class, [
+ 'choices' => [
+ 'Player' => 'ROLE_PLAYER',
+ 'Admin' => 'ROLE_ADMIN',
+ ],
+ 'multiple' => true,
+ 'expanded' => true,
+ 'label' => 'Roles',
+ ])
+ ->add('isVerified', CheckboxType::class, [
+ 'required' => false,
+ 'label' => 'Email verified',
+ ]);
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'data_class' => User::class,
+ ]);
+ }
+}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index f53ebfc..c9a9e34 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -23,6 +23,7 @@
{% endif %}
+{% block main %}
| ID | -Username | -Roles | -Verified | -|
|---|---|---|---|---|
| {{ player.id }} | -{{ player.username }} | -{{ player.email }} | -{{ player.roles|join(', ') }} | -{{ player.isVerified ? 'Yes' : 'No' }} | -
| No players found. | -||||
| ID | -Game | -Status | -Players Joined | -Created At | -Actions | -
|---|---|---|---|---|---|
| {{ session.id }} | -{{ session.game.name }} | -{{ session.status.value }} | -
-
|
- {{ session.created|date('Y-m-d H:i') }} | -- View Game Logs - | -
| No sessions found. | -|||||
{{ session.status.value }} · {{ session.players|length }} player(s) · Created {{ session.created|date('Y-m-d H:i') }}
+ + {% if playersLogs is empty %} +{{ playerLog.logs ?: 'No logs found for this player.' }}
+