mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-03-06 12:44:20 +01:00
Refactor Candidate and Quiz entities, rename Correction to QuizCandidate, and update related workflows
This commit removes nullable Uuid properties for consistency, transitions the Correction entity to QuizCandidate with associated migrations, refactors queries and repositories, adjusts related routes and controllers to use the new entity, updates front-end assets for elimination workflows, and standardizes route requirements and naming conventions.
This commit is contained in:
@@ -9,6 +9,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as AbstractBase
|
||||
|
||||
abstract class AbstractController extends AbstractBaseController
|
||||
{
|
||||
protected const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}';
|
||||
protected const string CANDIDATE_HASH_REGEX = '[\w\-=]+';
|
||||
|
||||
#[\Override]
|
||||
protected function addFlash(FlashType|string $type, mixed $message): void
|
||||
{
|
||||
|
||||
@@ -4,13 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\Correction;
|
||||
use App\Entity\QuizCandidate;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||
|
||||
class CorrectionCrudController extends AbstractCrudController
|
||||
{
|
||||
public static function getEntityFqcn(): string
|
||||
{
|
||||
return Correction::class;
|
||||
return QuizCandidate::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\Answer;
|
||||
use App\Entity\Candidate;
|
||||
use App\Entity\Correction;
|
||||
use App\Entity\GivenAnswer;
|
||||
use App\Entity\Question;
|
||||
use App\Entity\Quiz;
|
||||
use App\Entity\QuizCandidate;
|
||||
use App\Entity\Season;
|
||||
use App\Entity\User;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard;
|
||||
@@ -58,7 +58,7 @@ class DashboardController extends AbstractDashboardController
|
||||
yield MenuItem::linkToCrud('Quiz', 'fas fa-list', Quiz::class);
|
||||
yield MenuItem::linkToCrud('Question', 'fas fa-list', Question::class);
|
||||
yield MenuItem::linkToCrud('Candidate', 'fas fa-list', Candidate::class);
|
||||
yield MenuItem::linkToCrud('Correction', 'fas fa-list', Correction::class);
|
||||
yield MenuItem::linkToCrud('Correction', 'fas fa-list', QuizCandidate::class);
|
||||
yield MenuItem::linkToCrud('User', 'fas fa-list', User::class);
|
||||
yield MenuItem::linkToCrud('Given Answer', 'fas fa-list', GivenAnswer::class);
|
||||
yield MenuItem::linkToCrud('Answer', 'fas fa-list', Answer::class);
|
||||
|
||||
@@ -4,19 +4,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Backoffice;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Entity\Elimination;
|
||||
use App\Entity\Quiz;
|
||||
use App\Entity\Season;
|
||||
use App\Factory\EliminationFactory;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
final class PrepareEliminationController extends AbstractController
|
||||
{
|
||||
#[Route('/backoffice/elimination/{seasonCode}/{quiz}/prepare', name: 'app_prepare_elimination')]
|
||||
#[Route(
|
||||
'/backoffice/elimination/{seasonCode}/{quiz}/prepare',
|
||||
name: 'app_prepare_elimination',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||
)]
|
||||
public function index(Season $season, Quiz $quiz, EliminationFactory $eliminationFactory): Response
|
||||
{
|
||||
$elimination = $eliminationFactory->createEliminationFromQuiz($quiz);
|
||||
@@ -24,7 +28,11 @@ final class PrepareEliminationController extends AbstractController
|
||||
return $this->redirectToRoute('app_prepare_elimination_view', ['elimination' => $elimination->getId()]);
|
||||
}
|
||||
|
||||
#[Route('/backoffice/elimination/{elimination}', name: 'app_prepare_elimination_view')]
|
||||
#[Route(
|
||||
'/backoffice/elimination/{elimination}',
|
||||
name: 'app_prepare_elimination_view',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||
)]
|
||||
public function viewElimination(Elimination $elimination, Request $request, EntityManagerInterface $em): Response
|
||||
{
|
||||
if ('POST' === $request->getMethod()) {
|
||||
|
||||
@@ -23,7 +23,9 @@ class QuizController extends AbstractController
|
||||
private readonly CandidateRepository $candidateRepository,
|
||||
) {}
|
||||
|
||||
#[Route('/backoffice/season/{seasonCode}/quiz/{quiz}', name: 'app_backoffice_quiz')]
|
||||
#[Route('/backoffice/season/{seasonCode}/quiz/{quiz}', name: 'app_backoffice_quiz',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||
)]
|
||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||
public function index(Season $season, Quiz $quiz): Response
|
||||
{
|
||||
@@ -34,7 +36,9 @@ class QuizController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/backoffice/season/{seasonCode}/quiz/{quiz}/enable', name: 'app_backoffice_enable')]
|
||||
#[Route('/backoffice/season/{seasonCode}/quiz/{quiz}/enable', name: 'app_backoffice_enable',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||
)]
|
||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||
public function enableQuiz(Season $season, ?Quiz $quiz, EntityManagerInterface $em): Response
|
||||
{
|
||||
|
||||
@@ -29,7 +29,11 @@ class SeasonController extends AbstractController
|
||||
public function __construct(private readonly TranslatorInterface $translator, private EntityManagerInterface $em,
|
||||
) {}
|
||||
|
||||
#[Route('/backoffice/season/{seasonCode}', name: 'app_backoffice_season')]
|
||||
#[Route(
|
||||
'/backoffice/season/{seasonCode}',
|
||||
name: 'app_backoffice_season',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||
)]
|
||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||
public function index(Season $season): Response
|
||||
{
|
||||
@@ -38,7 +42,12 @@ class SeasonController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/backoffice/season/{seasonCode}/add_candidate', name: 'app_backoffice_add_candidates', priority: 10)]
|
||||
#[Route(
|
||||
'/backoffice/season/{seasonCode}/add_candidate',
|
||||
name: 'app_backoffice_add_candidates',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||
priority: 10,
|
||||
)]
|
||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||
public function addCandidates(Season $season, Request $request): Response
|
||||
{
|
||||
@@ -59,7 +68,12 @@ class SeasonController extends AbstractController
|
||||
return $this->render('backoffice/season_add_candidates.html.twig', ['form' => $form]);
|
||||
}
|
||||
|
||||
#[Route('/backoffice/season/{seasonCode}/add', name: 'app_backoffice_quiz_add', priority: 10)]
|
||||
#[Route(
|
||||
'/backoffice/season/{seasonCode}/add',
|
||||
name: 'app_backoffice_quiz_add',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||
priority: 10,
|
||||
)]
|
||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||
public function addQuiz(Request $request, Season $season, QuizSpreadsheetService $quizSpreadsheet): Response
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Entity\Answer;
|
||||
use App\Entity\Candidate;
|
||||
use App\Entity\GivenAnswer;
|
||||
use App\Entity\Question;
|
||||
use App\Entity\Quiz;
|
||||
use App\Entity\Season;
|
||||
use App\Enum\FlashType;
|
||||
use App\Form\EnterNameType;
|
||||
@@ -17,6 +18,7 @@ use App\Repository\AnswerRepository;
|
||||
use App\Repository\CandidateRepository;
|
||||
use App\Repository\GivenAnswerRepository;
|
||||
use App\Repository\QuestionRepository;
|
||||
use App\Repository\QuizCandidateRepository;
|
||||
use App\Repository\SeasonRepository;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
@@ -29,13 +31,9 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
#[AsController]
|
||||
final class QuizController extends AbstractController
|
||||
{
|
||||
public const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}';
|
||||
|
||||
private const string CANDIDATE_HASH_REGEX = '[\w\-=]+';
|
||||
|
||||
public function __construct(private readonly TranslatorInterface $translator) {}
|
||||
|
||||
#[Route(path: '/', name: 'app_quiz_selectseason', methods: ['GET', 'POST'])]
|
||||
#[Route(path: '/', name: 'app_quiz_select_season', methods: ['GET', 'POST'])]
|
||||
public function selectSeason(Request $request, SeasonRepository $seasonRepository): Response
|
||||
{
|
||||
$form = $this->createForm(SelectSeasonType::class);
|
||||
@@ -47,16 +45,16 @@ final class QuizController extends AbstractController
|
||||
if ([] === $seasonRepository->findBy(['seasonCode' => $seasonCode])) {
|
||||
$this->addFlash(FlashType::Warning, $this->translator->trans('Invalid season code'));
|
||||
|
||||
return $this->redirectToRoute('app_quiz_selectseason');
|
||||
return $this->redirectToRoute('app_quiz_select_season');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $seasonCode]);
|
||||
return $this->redirectToRoute('app_quiz_enter_name', ['seasonCode' => $seasonCode]);
|
||||
}
|
||||
|
||||
return $this->render('quiz/select_season.html.twig', ['form' => $form]);
|
||||
}
|
||||
|
||||
#[Route(path: '/{seasonCode}', name: 'app_quiz_entername', requirements: ['seasonCode' => self::SEASON_CODE_REGEX])]
|
||||
#[Route(path: '/{seasonCode}', name: 'app_quiz_enter_name', requirements: ['seasonCode' => self::SEASON_CODE_REGEX])]
|
||||
public function enterName(
|
||||
Request $request,
|
||||
#[MapEntity(mapping: ['seasonCode' => 'seasonCode'])]
|
||||
@@ -69,7 +67,7 @@ final class QuizController extends AbstractController
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$name = $form->get('name')->getData();
|
||||
|
||||
return $this->redirectToRoute('app_quiz_quizpage', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => Base64::base64UrlEncode($name)]);
|
||||
return $this->redirectToRoute('app_quiz_quiz_page', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => Base64::base64UrlEncode($name)]);
|
||||
}
|
||||
|
||||
return $this->render('quiz/enter_name.twig', ['season' => $season, 'form' => $form]);
|
||||
@@ -77,25 +75,34 @@ final class QuizController extends AbstractController
|
||||
|
||||
#[Route(
|
||||
path: '/{seasonCode}/{nameHash}',
|
||||
name: 'app_quiz_quizpage',
|
||||
name: 'app_quiz_quiz_page',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX],
|
||||
)]
|
||||
public function quizPage(
|
||||
#[MapEntity(mapping: ['seasonCode' => 'seasonCode'])]
|
||||
Season $season,
|
||||
string $nameHash,
|
||||
Request $request,
|
||||
CandidateRepository $candidateRepository,
|
||||
QuestionRepository $questionRepository,
|
||||
AnswerRepository $answerRepository,
|
||||
GivenAnswerRepository $givenAnswerRepository,
|
||||
Request $request,
|
||||
QuizCandidateRepository $quizCandidateRepository,
|
||||
): Response {
|
||||
$candidate = $candidateRepository->getCandidateByHash($season, $nameHash);
|
||||
|
||||
if (!$candidate instanceof Candidate) {
|
||||
$this->addFlash(FlashType::Danger, $this->translator->trans('Candidate not found'));
|
||||
|
||||
return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]);
|
||||
return $this->redirectToRoute('app_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
|
||||
}
|
||||
|
||||
$quiz = $season->getActiveQuiz();
|
||||
|
||||
if (!$quiz instanceof Quiz) {
|
||||
$this->addFlash(FlashType::Warning, $this->translator->trans('There is no active quiz'));
|
||||
|
||||
return $this->redirectToRoute('app_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
|
||||
}
|
||||
|
||||
if ('POST' === $request->getMethod()) {
|
||||
@@ -105,13 +112,10 @@ final class QuizController extends AbstractController
|
||||
throw new BadRequestException('Invalid Answer ID');
|
||||
}
|
||||
|
||||
$givenAnswer = (new GivenAnswer())
|
||||
->setCandidate($candidate)
|
||||
->setAnswer($answer)
|
||||
->setQuiz($answer->getQuestion()->getQuiz());
|
||||
$givenAnswer = new GivenAnswer($candidate, $answer->getQuestion()->getQuiz(), $answer);
|
||||
$givenAnswerRepository->save($givenAnswer);
|
||||
|
||||
return $this->redirectToRoute('app_quiz_quizpage', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => $nameHash]);
|
||||
return $this->redirectToRoute('app_quiz_quiz_page', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => $nameHash]);
|
||||
}
|
||||
|
||||
$question = $questionRepository->findNextQuestionForCandidate($candidate);
|
||||
@@ -119,10 +123,11 @@ final class QuizController extends AbstractController
|
||||
if (!$question instanceof Question) {
|
||||
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz completed'));
|
||||
|
||||
return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]);
|
||||
return $this->redirectToRoute('app_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
|
||||
}
|
||||
|
||||
// TODO One first question record time
|
||||
$quizCandidateRepository->createIfNotExist($quiz, $candidate);
|
||||
|
||||
return $this->render('quiz/question.twig', ['candidate' => $candidate, 'question' => $question]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user