Refactor routes for consistency, update season codes, and add Justfile for Docker commands

This commit is contained in:
2025-04-02 22:10:30 +02:00
parent acf5c06fcc
commit 31e6ed406b
17 changed files with 652 additions and 731 deletions

View File

@@ -6,7 +6,6 @@
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" /> <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" /> <excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" /> <excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" /> <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" /> <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" /> <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" />
@@ -48,8 +47,6 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/rector/rector" /> <excludeFolder url="file://$MODULE_DIR$/vendor/rector/rector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/runtime/frankenphp-symfony" /> <excludeFolder url="file://$MODULE_DIR$/vendor/runtime/frankenphp-symfony" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" /> <excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" /> <excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" /> <excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" /> <excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
@@ -134,6 +131,7 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-fixtures-bundle" /> <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/vincentlanglet/twig-cs-fixer" /> <excludeFolder url="file://$MODULE_DIR$/vendor/vincentlanglet/twig-cs-fixer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" /> <excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php84" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

4
.idea/php.xml generated
View File

@@ -70,7 +70,6 @@
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" /> <path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" /> <path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" /> <path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
<path value="$PROJECT_DIR$/vendor/doctrine/cache" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" /> <path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" /> <path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" /> <path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
@@ -112,8 +111,6 @@
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" /> <path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" /> <path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" /> <path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" /> <path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" /> <path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" /> <path value="$PROJECT_DIR$/vendor/phar-io/version" />
@@ -164,6 +161,7 @@
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" /> <path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<path value="$PROJECT_DIR$/vendor/webmozart/assert" /> <path value="$PROJECT_DIR$/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/vendor/vincentlanglet/twig-cs-fixer" /> <path value="$PROJECT_DIR$/vendor/vincentlanglet/twig-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php84" />
</include_path> </include_path>
</component> </component>
<component name="PhpInterpreters"> <component name="PhpInterpreters">

24
Justfile Normal file
View File

@@ -0,0 +1,24 @@
up:
docker compose up -d
down *args:
docker compose down {{ args }} --remove-orphans
stop:
docker compose stop
exec *args:
docker compose exec php {{ args }}
[no-exit-message]
shell:
@docker compose exec php bash
migrate: up
docker compose run --rm php bin/console doctrine:migrations:migrate --no-interaction
fixtures:
docker compose exec php bin/console doctrine:fixtures:load --purge-with-truncate --no-interaction
translations:
docker compose exec php bin/console translation:extract --domain=messages --force --format=yaml --sort=asc --clean nl

View File

@@ -1,22 +0,0 @@
version: '3'
tasks:
up:
cmds:
- docker compose up -d
down:
cmds:
- docker compose down
stop:
cmds:
- docker compose stop
shell:
cmds:
- docker compose exec app bash
migrate:
cmds:
- docker compose run php bin/console doctrine:migrations:migrate
translations:
cmds:
- docker compose exec php bin/console translation:extract --domain=messages --force --format=yaml --sort=asc --clean nl

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
declare(strict_types=1);
use App\Kernel; use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Console\Application;

View File

@@ -10,7 +10,7 @@
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"doctrine/dbal": "^4.2.3", "doctrine/dbal": "^4.2.3",
"doctrine/doctrine-bundle": "^2.13.2", "doctrine/doctrine-bundle": "^2.14.0",
"doctrine/doctrine-migrations-bundle": "^3.4.1", "doctrine/doctrine-migrations-bundle": "^3.4.1",
"doctrine/orm": "^3.3.2", "doctrine/orm": "^3.3.2",
"easycorp/easyadmin-bundle": "^4.24.5", "easycorp/easyadmin-bundle": "^4.24.5",
@@ -26,19 +26,19 @@
"symfony/twig-bundle": "7.2.*", "symfony/twig-bundle": "7.2.*",
"symfony/uid": "7.2.*", "symfony/uid": "7.2.*",
"symfony/yaml": "7.2.*", "symfony/yaml": "7.2.*",
"thecodingmachine/safe": "^2.5" "thecodingmachine/safe": "^3.0.2"
}, },
"require-dev": { "require-dev": {
"roave/security-advisories": "dev-latest", "doctrine/doctrine-fixtures-bundle": "^4.1",
"doctrine/doctrine-fixtures-bundle": "^4.0", "friendsofphp/php-cs-fixer": "^3.75.0",
"friendsofphp/php-cs-fixer": "^3.71.0",
"phpstan/extension-installer": "^1.4.3", "phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.1.8", "phpstan/phpstan": "^2.1.11",
"phpstan/phpstan-doctrine": "^2.0.2", "phpstan/phpstan-doctrine": "^2.0.2",
"phpstan/phpstan-phpunit": "^2.0.4", "phpstan/phpstan-phpunit": "^2.0.6",
"phpstan/phpstan-symfony": "^2.0.2", "phpstan/phpstan-symfony": "^2.0.4",
"phpunit/phpunit": "^11.5.12", "phpunit/phpunit": "^12.0.10",
"rector/rector": "^2.0.10", "rector/rector": "^2.0.11",
"roave/security-advisories": "dev-latest",
"symfony/maker-bundle": "^1.62.1", "symfony/maker-bundle": "^1.62.1",
"symfony/stopwatch": "7.2.*", "symfony/stopwatch": "7.2.*",
"symfony/web-profiler-bundle": "7.2.*", "symfony/web-profiler-bundle": "7.2.*",

1220
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250402185128 extends AbstractMigration
{
public function getDescription(): string
{
return 'add elimination table';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE elimination (id UUID NOT NULL, data JSON NOT NULL, PRIMARY KEY(id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE elimination');
}
}

View File

@@ -27,7 +27,7 @@ class TestCommand extends Command
{ {
new SymfonyStyle($input, $output); new SymfonyStyle($input, $output);
dd($this->candidateRepository->getScores($this->quizRepository->find('1effa06a-8aca-6c52-b52b-3974eda7eed7'))); dd($this->candidateRepository->getScores($this->quizRepository->find('1f00ff44-6f12-630e-9b87-67e78e97c05e')));
return Command::SUCCESS; return Command::SUCCESS;
} }

View File

@@ -21,7 +21,7 @@ final class BackofficeController extends AbstractController
private readonly CandidateRepository $candidateRepository, private readonly CandidateRepository $candidateRepository,
) {} ) {}
#[Route('/backoffice/', name: 'index')] #[Route('/backoffice/', name: 'app_backoffice_index')]
public function index(): Response public function index(): Response
{ {
$seasons = $this->seasonRepository->findAll(); $seasons = $this->seasonRepository->findAll();
@@ -31,7 +31,7 @@ final class BackofficeController extends AbstractController
]); ]);
} }
#[Route('/backoffice/{seasonCode}', name: 'season')] #[Route('/backoffice/{seasonCode}', name: 'app_backoffice_season')]
public function season(Season $season): Response public function season(Season $season): Response
{ {
return $this->render('backoffice/season.html.twig', [ return $this->render('backoffice/season.html.twig', [
@@ -39,7 +39,7 @@ final class BackofficeController extends AbstractController
]); ]);
} }
#[Route('/backoffice/{seasonCode}/{quiz}', name: 'quiz')] #[Route('/backoffice/{seasonCode}/{quiz}', name: 'app_backoffice_quiz')]
public function quiz(Season $season, Quiz $quiz): Response public function quiz(Season $season, Quiz $quiz): Response
{ {
return $this->render('backoffice/quiz.html.twig', [ return $this->render('backoffice/quiz.html.twig', [

View File

@@ -11,7 +11,7 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends AbstractController class LoginController extends AbstractController
{ {
#[Route(path: '/login', name: 'app_login')] #[Route(path: '/login', name: 'app_login_login')]
public function login(AuthenticationUtils $authenticationUtils): Response public function login(AuthenticationUtils $authenticationUtils): Response
{ {
// get the login error if there is one // get the login error if there is one
@@ -26,7 +26,7 @@ class LoginController extends AbstractController
]); ]);
} }
#[Route(path: '/logout', name: 'app_logout')] #[Route(path: '/logout', name: 'app_login_logout')]
public function logout(): void public function logout(): void
{ {
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');

View File

@@ -31,7 +31,7 @@ final class QuizController extends AbstractController
private const string CANDIDATE_HASH_REGEX = '[\w\-=]+'; private const string CANDIDATE_HASH_REGEX = '[\w\-=]+';
#[Route(path: '/', name: 'select_season', methods: ['GET', 'POST'])] #[Route(path: '/', name: 'app_quiz_selectseason', methods: ['GET', 'POST'])]
public function selectSeason(Request $request): Response public function selectSeason(Request $request): Response
{ {
$form = $this->createForm(SelectSeasonType::class); $form = $this->createForm(SelectSeasonType::class);
@@ -40,13 +40,13 @@ final class QuizController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData(); $data = $form->getData();
return $this->redirectToRoute('enter_name', ['seasonCode' => $data['season_code']]); return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $data['season_code']]);
} }
return $this->render('quiz/select_season.html.twig', ['form' => $form]); return $this->render('quiz/select_season.html.twig', ['form' => $form]);
} }
#[Route(path: '/{seasonCode}', name: 'enter_name', requirements: ['seasonCode' => self::SEASON_CODE_REGEX])] #[Route(path: '/{seasonCode}', name: 'app_quiz_entername', requirements: ['seasonCode' => self::SEASON_CODE_REGEX])]
public function enterName( public function enterName(
Request $request, Request $request,
#[MapEntity(mapping: ['seasonCode' => 'seasonCode'])] #[MapEntity(mapping: ['seasonCode' => 'seasonCode'])]
@@ -60,7 +60,7 @@ final class QuizController extends AbstractController
$data = $form->getData(); $data = $form->getData();
$name = $data['name']; $name = $data['name'];
return $this->redirectToRoute('quiz_page', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => Base64::base64_url_encode($name)]); return $this->redirectToRoute('app_quiz_quizpage', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => Base64::base64_url_encode($name)]);
} }
return $this->render('quiz/enter_name.twig', ['season' => $season, 'form' => $form]); return $this->render('quiz/enter_name.twig', ['season' => $season, 'form' => $form]);
@@ -68,7 +68,7 @@ final class QuizController extends AbstractController
#[Route( #[Route(
path: '/{seasonCode}/{nameHash}', path: '/{seasonCode}/{nameHash}',
name: 'quiz_page', name: 'app_quiz_quizpage',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX], requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX],
)] )]
public function quizPage( public function quizPage(
@@ -87,7 +87,7 @@ final class QuizController extends AbstractController
if ($season->isPreregisterCandidates()) { if ($season->isPreregisterCandidates()) {
$this->addFlash(FlashType::Danger, 'Candidate not found'); $this->addFlash(FlashType::Danger, 'Candidate not found');
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]); return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]);
} }
$candidate = new Candidate(Base64::base64_url_decode($nameHash)); $candidate = new Candidate(Base64::base64_url_decode($nameHash));
@@ -113,7 +113,7 @@ final class QuizController extends AbstractController
if (!$question instanceof Question) { if (!$question instanceof Question) {
$this->addFlash(FlashType::Success, 'Quiz completed'); $this->addFlash(FlashType::Success, 'Quiz completed');
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]); return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]);
} }
// TODO One first question record time // TODO One first question record time

View File

@@ -20,7 +20,7 @@ class KrtekFixtures extends Fixture
$manager->persist($season); $manager->persist($season);
$season->setName('Krtek Weekend') $season->setName('Krtek Weekend')
->setSeasonCode('12345') ->setSeasonCode('krtek')
->setPreregisterCandidates(true) ->setPreregisterCandidates(true)
->addCandidate(new Candidate('Claudia')) ->addCandidate(new Candidate('Claudia'))
->addCandidate(new Candidate('Eelco')) ->addCandidate(new Candidate('Eelco'))

View File

@@ -26,7 +26,7 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<a {% if season.activeQuiz %}href="{{ path('enter_name', {seasonCode: season.seasonCode}) }}" <a {% if season.activeQuiz %}href="{{ path('app_quiz_entername', {seasonCode: season.seasonCode}) }}"
{% else %}class="disabled" {% endif %}>{{ season.seasonCode }}</a> {% else %}class="disabled" {% endif %}>{{ season.seasonCode }}</a>
</td> </td>
<td> <td>
@@ -37,7 +37,7 @@
aria-label="Preregister Enabled"> aria-label="Preregister Enabled">
</td> </td>
<td> <td>
<a href="{{ path('backoffice_season', {seasonCode: season.seasonCode}) }}">{% trans %}Manage{% endtrans %}</a> <a href="{{ path('app_backoffice_season', {seasonCode: season.seasonCode}) }}">{% trans %}Manage{% endtrans %}</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}

View File

@@ -13,8 +13,8 @@
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if 'backoffice_index' == app.current_route() %} active{% endif %}" <a class="nav-link{% if 'app_backoffice_index' == app.current_route() %} active{% endif %}"
href="{{ path('backoffice_index') }}">{{ 'Seasons'|trans }}</a> href="{{ path('app_backoffice_index') }}">{{ 'Seasons'|trans }}</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -9,7 +9,7 @@
<div class="list-group"> <div class="list-group">
{% for quiz in season.quizzes %} {% for quiz in season.quizzes %}
<a class="list-group-item list-group-item-action{% if season.activeQuiz == quiz %} active{% endif %}" <a class="list-group-item list-group-item-action{% if season.activeQuiz == quiz %} active{% endif %}"
href="{{ path('backoffice_quiz', {seasonCode: season.seasonCode, quiz: quiz.id}) }}">{{ quiz.name }}</a> href="{{ path('app_backoffice_quiz', {seasonCode: season.seasonCode, quiz: quiz.id}) }}">{{ quiz.name }}</a>
{% else %} {% else %}
No quizzes No quizzes
{% endfor %} {% endfor %}

View File

@@ -16,22 +16,24 @@
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1> <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="username">Email</label> <label for="username">Email</label>
<input type="email" value="{{ last_username }}" name="_username" id="username" class="form-control" autocomplete="email" required autofocus> <input type="email" value="{{ last_username }}" name="_username" id="username" class="form-control"
autocomplete="email" required autofocus>
<label for="password">Password</label> <label for="password">Password</label>
<input type="password" name="_password" id="password" class="form-control" autocomplete="current-password" required> <input type="password" name="_password" id="password" class="form-control" autocomplete="current-password"
required>
<input type="hidden" name="_csrf_token" <input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}" value="{{ csrf_token('authenticate') }}"
> >
{# {#
Uncomment this section and add a remember_me option below your firewall to activate remember me functionality. Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
See https://symfony.com/doc/current/security/remember_me.html See https://symfony.com/doc/current/security/remember_me.html
<div class="checkbox mb-3"> <div class="checkbox mb-3">
<input type="checkbox" name="_remember_me" id="_remember_me"> <input type="checkbox" name="_remember_me" id="_remember_me">
<label for="_remember_me">Remember me</label> <label for="_remember_me">Remember me</label>
</div> </div>
#} #}
<button class="btn btn-lg btn-primary" type="submit"> <button class="btn btn-lg btn-primary" type="submit">