diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 6402eff..38e0118 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -47,6 +47,5 @@ security: # Note: Only the *first* access control that matches will be used access_control: - { path: ^/docs, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI - - { path: ^/login, roles: PUBLIC_ACCESS } - - { path: ^/test, roles: PUBLIC_ACCESS } # TODO: Remove it in production + - { path: ^/login_check, roles: PUBLIC_ACCESS } - { path: ^/, roles: IS_AUTHENTICATED_FULLY } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bef10ac..271a5b0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,7 +6,7 @@ backupGlobals="false" colors="true" bootstrap="tests/bootstrap.php" - cacheResultFile="var/.phpunit.result.cache" + cacheResultFile="var/cache/test/.phpunit.result.cache" convertDeprecationsToExceptions="false" > diff --git a/src/Command/AddProjectCommand.php b/src/Command/AddProjectCommand.php new file mode 100644 index 0000000..345e7ed --- /dev/null +++ b/src/Command/AddProjectCommand.php @@ -0,0 +1,243 @@ +setName(self::$defaultName) + ->setDescription('Create a project and store it in the database') + ->setHelp(self::getCommandHelp()) + ->addArgument( + name:'name', + mode: InputArgument::OPTIONAL, + description: 'The project name' + ) + ->addArgument( + name: 'owner-username', + mode: InputArgument::OPTIONAL, + description: 'The project owner username' + ); + } + + /** + * {@inheritDoc} + */ + protected function initialize( + InputInterface $input, + OutputInterface $output + ): void + { + // SymfonyStyle is an optional feature that Symfony provides so you can + // apply a consistent look to the commands of your application. + // See https://symfony.com/doc/current/console/style.html + $this->io = new SymfonyStyle($input, $output); + $this->faker = Factory::create('fr_CH'); + } + + /** + * {@inheritDoc} + */ + protected function interact( + InputInterface $input, + OutputInterface $output + ): void + { + /** @var string|null $name */ + $name = $input->getArgument('name'); + + /** @var string|null $ownerUsername */ + $ownerUsername = $input->getArgument('owner-username'); + + /** @var User|null $owner */ + $owner = $this->em->getRepository(User::class)->findOneBy([ + 'username' => $ownerUsername + ]); + + $project = (new Project) + ->setName($name) + ->setUser($owner); + + $errors = $this->validator->validate($project); + + if (count($errors) > 0) { + $this->io->title('SmartERD - Project creator assistant'); + $this->io->text( + 'Some values were not provided / missing or invalid.' + ); + $this->io->text( + 'Answer all the questions or press to choose the '. + '[default or previous value]' + ); + } + + while ($errors->count() > 0) { + /** @var string|null $name */ + $name = $this->io->ask( + question: 'Name', + default: $name ?: $this->faker->company() + ); + + $project->setName($name); + + /** @var string|null $ownerUsername */ + $ownerUsername = $this->io->ask( + question: 'Owner Username', + default: $ownerUsername ?: UserFixtures::USER_USERNAME + ); + + /** @var User|null $owner */ + $owner = $this->em->getRepository(User::class)->findOneBy([ + 'username' => $ownerUsername + ]); + + $project->setUser($owner); + + $errors = $this->validator->validate($project); + + /** @var ConstraintViolationInterface $error */ + foreach ($errors as $error) { + $this->io->error($error->getPropertyPath() . ' ' .$error->getMessage()); + } + } + + $input->setArgument('name', $name); + $input->setArgument('owner-username', $ownerUsername); + } + + /** + * This method is executed after interact() and initialize(). It usually + * contains the logic to execute to complete this command task. + * + * {@inheritDoc} + * + * @return Command::SUCCESS|Command::INVALID|Command::FAILURE + */ + protected function execute( + InputInterface $input, + OutputInterface $output + ): int + { + $stopwatch = new Stopwatch(); + $stopwatch->start(self::$defaultName); + + /** @var string $name */ + $name = $input->getArgument('name'); + + /** @var string $ownerUsername */ + $ownerUsername = $input->getArgument('owner-username'); + + /** @var User|null $owner */ + $owner = $this->em->getRepository(User::class)->findOneBy([ + 'username' => $ownerUsername + ]); + + $project = (new Project) + ->setName($name) + ->setUser($owner); + + $this->em->persist($project); + + $errors = $this->validator->validate($project); + + if (count($errors) > 0) { + /** @var ConstraintViolationInterface $error */ + foreach ($errors as $error) { + $this->io->error($error->getPropertyPath() . ' ' .$error->getMessage()); + } + + $this->io->error('The user has not been created.'); + + return Command::FAILURE; + } + + $this->em->flush(); + + $this->io->text(sprintf( + 'This project has been created:' . PHP_EOL . + ' Slug: %s'. PHP_EOL . + ' Name: %s'. PHP_EOL . + ' Owner: %s', + $project->getSlug(), + $project->getName(), + $ownerUsername + )); + + $event = $stopwatch->stop(self::$defaultName); + + if ($output->isVerbose()) { + $this->io->comment(sprintf(<<getDuration(), + $event->getMemory() + )); + } + + return Command::SUCCESS; + } + + /** + * The command help is usually included in the "configure()" method, but + * when it's too long, it's better to define a separate method to maintain + * the code readability. + */ + private static function getCommandHelp(): string + { + return <<<'HELP' +The %command.name% command create a new project and save it in the +database: + + php %command.full_name% name owner-username + +If you omit any of the two required arguments, the command will ask you to +provide the missing values: + + # command will ask you for the name + php %command.full_name% owner-username + + # command will ask you for the owner-username + php %command.full_name% name + + # command wil ask you for all arguments + php %command.full_name% +HELP; + } +} diff --git a/src/Command/EntityCreateCommand.php b/src/Command/EntityCreateCommand.php deleted file mode 100644 index e8b0027..0000000 --- a/src/Command/EntityCreateCommand.php +++ /dev/null @@ -1,123 +0,0 @@ -faker = Factory::create('fr_CH'); - - parent::__construct(); - } - - /** - * {@inheritDoc} - */ - protected function configure(): void - { - $this - ->setName('erd:entity:create') - ->setDescription('Create an ERD Entity by asking few questions') - ->addArgument('ownerUsername', InputArgument::OPTIONAL, 'The owner name') - ->addArgument('projectName', InputArgument::OPTIONAL, 'The project name') - ->addArgument('name', InputArgument::OPTIONAL, 'The project title') - ; - } - - /** - * {@inheritDoc} - */ - protected function execute( - InputInterface $input, - OutputInterface $output - ): int - { - $io = new SymfonyStyle($input, $output); - $io->title('SmartERD - Entity creator assistant'); - $io->text('Answer all the questions or press to choose the [default value]'); - - // TODO: Add validator to questions when Reusable sets of constraints - // will be available in Symfony 5.1 - $ownerUsername = $input->getArgument('ownerUsername'); - - /** @var User $owner */ - $owner = $this->em->getRepository(User::class)->findOneBy([ - 'username' => $ownerUsername - ]); - - while (null === $ownerUsername && null === $owner) { - $ownerUsername = $io->ask('Owner username', UserFixtures::USER_USERNAME); - $owner = $this->em->getRepository(User::class)->findOneBy([ - 'username' => $ownerUsername - ]); - } - - $projectName = $input->getArgument('projectName'); - - $project = null; - - // TODO: Check the 2 following condition and correct them. Same for other file - /** @var Project $project */ - if (null !== $projectName) { - $project = $this->em - ->getRepository(Project::class) - ->findOneByUserAndName($owner, $projectName); - } - - while (null === $projectName && null === $project) { - $projectName = $io->ask('Project name', ProjectFixtures::USER_PROJECT_NAME_1); - /** - * @var Project $project - */ - $project = $this->em - ->getRepository(Project::class) - ->findOneByUserAndName($owner, $projectName); - } - - $name = $input->getArgument('name'); - while (null === $name) { - $name = $io->ask('Name', $this->faker->jobTitle); - } - - $ok = $io->confirm( - 'This project will be created:' . PHP_EOL . - ' Owner: ' . $ownerUsername . '' . PHP_EOL . - ' Project: ' . $projectName . '' . PHP_EOL . - ' Name: ' . $name . '' . PHP_EOL . - ' Is everything correct ?' - ); - - if ($ok) { - try { - $this->em->persist( - (new Entity) - ->setName($name) - ->setProject($project) - ); - $this->em->flush(); - $io->success(sprintf('The entity %s has been created', $name)); - } catch (\Exception) { - // $io->error($e->getMessage()); - $io->error('An error occurred, please try again'); - } - } else { - $io->error('Relaunch this command to try again'); - } - - return Command::SUCCESS; - } -} diff --git a/src/Command/ProjectCreateCommand.php b/src/Command/ProjectCreateCommand.php deleted file mode 100644 index eb35c6d..0000000 --- a/src/Command/ProjectCreateCommand.php +++ /dev/null @@ -1,104 +0,0 @@ -faker = Factory::create('fr_CH'); - - parent::__construct(); - } - - /** - * {@inheritDoc} - */ - protected function configure(): void - { - $this - ->setName('erd:project:create') - ->setDescription('Create an project by asking few questions') - ->addArgument('name', InputArgument::OPTIONAL, 'The project name') - ->addArgument('ownerUsername', InputArgument::OPTIONAL, 'The owner username') - ; - } - - /** - * {@inheritDoc} - */ - protected function execute( - InputInterface $input, - OutputInterface $output - ): int - { - $io = new SymfonyStyle($input, $output); - $io->title('SmartERD - Project creator assistant'); - $io->text('Answer all the questions or press to choose the [default value]'); - - /** - * TODO: Add validator to questions when Reusable sets of constraints - * will be available in Symfony 5.1 - */ - - $name = $input->getArgument('name'); - while (null === $name) { - $name = $io->ask('name', $this->faker->company); - } - - $ownerUsername = $input->getArgument('ownerUsername'); - - /** - * @var User $owner - */ - $owner = $this->em->getRepository(User::class)->findOneBy([ - 'username' => $ownerUsername - ]); - - while (null === $ownerUsername && $owner === null) { - $ownerUsername = $io->ask('Owner username', UserFixtures::USER_USERNAME); - $owner = $this->em->getRepository(User::class)->findOneBy([ - 'username' => $ownerUsername - ]); - } - - $ok = $io->confirm( - 'This project will be created:' . PHP_EOL . - ' Name: ' . $name . '' . PHP_EOL . - ' Owner: ' . $ownerUsername . '' . PHP_EOL . - ' Is everything correct ?' - ); - - if ($ok) { - try { - $this->em->persist( - (new Project) - ->setName($name) - ->setUser($owner) - ); - $this->em->flush(); - $io->success(sprintf('The project %s has been created', $name)); - } catch (\Exception) { - // $io->error($e->getMessage()); - $io->error('An error occurred, please try again'); - } - } else { - $io->error('Relaunch this command to try again'); - } - - return 0; - } -} diff --git a/src/Command/UserCreateCommand.php b/src/Command/UserCreateCommand.php deleted file mode 100644 index 8a1f14d..0000000 --- a/src/Command/UserCreateCommand.php +++ /dev/null @@ -1,109 +0,0 @@ -faker = Factory::create('fr_CH'); - - parent::__construct(); - } - - /** - * {@inheritDoc} - */ - protected function configure(): void - { - $this - ->setName('app:user:create') - ->setDescription('Create an user by asking few questions') - ->addArgument('username', InputArgument::OPTIONAL, 'The user username') - ->addArgument('email', InputArgument::OPTIONAL, 'The user email') - ->addArgument('isAdmin', InputArgument::OPTIONAL, 'If the user is admin or not') - ->addArgument('plainPassword', InputArgument::OPTIONAL, 'The plain password (not hashed)') - ; - } - - /** - * {@inheritDoc} - */ - protected function execute( - InputInterface $input, - OutputInterface $output - ): int - { - $io = new SymfonyStyle($input, $output); - $io->title('SmartERD - User creator assistant'); - $io->text('Answer all the questions or press to choose the [default value]'); - - /** - * TODO: Add validator to questions when Reusable sets of constraints - * will be available in Symfony 5.1 - */ - - $username = $input->getArgument('username'); - while (null === $username) { - $username = $io->ask('Username', $this->faker->userName); - } - - $email = $input->getArgument('email'); - while (null === $email) { - $email = $io->ask('Email', $this->faker->freeEmail); - } - - $isAdmin = $input->getArgument('isAdmin'); - while (!is_bool($isAdmin)) { - $isAdmin = $io->confirm('Is the user an administrator ?', false); - } - - $plainPassword = $input->getArgument('plainPassword'); - while (null === $plainPassword) { - $plainPassword = $io->ask('Password', UserFixtures::DEFAULT_USER_PASSWORD); - } - - $ok = $io->confirm( - 'This user will be created:' . PHP_EOL . - ' Username: ' . $username . '' . PHP_EOL . - ' Email: ' . $email . '' . PHP_EOL . - ' Is administrator: ' . ($isAdmin ? 'Yes' : 'No') . '' . PHP_EOL . - ' Plain password: ' . $plainPassword . '' . PHP_EOL . - ' Is everything correct ?' - ); - - if ($ok) { - try { - $this->em->persist( - (new User) - ->setUsername($username) - ->setEmail($email) - ->setIsAdmin($isAdmin) - ->setPlainPassword($plainPassword) - ); - $this->em->flush(); - $io->success(sprintf('The user %s has been created', $username)); - } catch (\Exception) { - // $io->error($e->getMessage()); - $io->error('An error occurred, please try again'); - } - } else { - $io->error('Relaunch this command to try again'); - } - - return 0; - } -} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php deleted file mode 100644 index e8cf54d..0000000 --- a/src/Controller/SecurityController.php +++ /dev/null @@ -1,48 +0,0 @@ -getUser(); - - // It's null only if firewall hasn't been configured. - if (!$currentUser) { - $response = new JsonResponse([ - 'erreur' => 'Authentification nécessaire' - ]); - return $response - ->setEncodingOptions(JSON_UNESCAPED_UNICODE) - ->setStatusCode(Response::HTTP_BAD_REQUEST) - ; - } - - $token = $this->encoder->encode([ - 'sub' => $currentUser->getId(), - 'username' => $currentUser->getUserIdentifier(), - 'roles' => $currentUser->getRoles(), - 'exp' => time() + 1800 // 30 minutes expiration - ]); - - return new JsonResponse(['token' => $token]); - } -} diff --git a/src/Controller/TestController.php b/src/Controller/TestController.php deleted file mode 100644 index 85e8b5e..0000000 --- a/src/Controller/TestController.php +++ /dev/null @@ -1,25 +0,0 @@ -denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); - $this->denyAccessUnlessGranted('ROLE_ADMIN'); - ob_start(); - phpinfo(); - $response = ob_get_contents(); - ob_get_clean(); - - return new Response($response); - } -} diff --git a/src/Validator/Constraints/Project/ProjectUserRequirements.php b/src/Validator/Constraints/Project/ProjectUserRequirements.php new file mode 100644 index 0000000..c07cf32 --- /dev/null +++ b/src/Validator/Constraints/Project/ProjectUserRequirements.php @@ -0,0 +1,38 @@ +getSingleScalarResult() + 1; + /** @var int $entriesCount */ + $entriesCount = $query->getSingleScalarResult() + 1; } catch (NoResultException|NonUniqueResultException) { $this->context->buildViolation('Request server side is wrong formatted. Please contact the website administrator.') diff --git a/tests/Unit/Entity/AttributesTest.php b/tests/Unit/Entity/AttributesTest.php index 9a39e68..7d22cc4 100644 --- a/tests/Unit/Entity/AttributesTest.php +++ b/tests/Unit/Entity/AttributesTest.php @@ -66,17 +66,11 @@ public function testSetPositionManually(): void $violations = $this->validator->validate($attribute); - $errors = (string) $violations; - - $expectedError = str_replace( - ['{{ lastPosition }}', '{{ givenPosition }}'], - [0, 127], - NoHolesInPosition::MESSAGE - ); - self::assertCount(1, $violations); - self::assertStringContainsString($expectedError, $errors); - + self::assertStringContainsString( + needle: NoHolesInPosition::MESSAGE_IF_NO_OTHER_ATTRIBUTES, + haystack: (string) $violations + ); } public function testPushAttributeAtTheEnd(): void