[Feature] Passage vers Symfony #1

Merged
Skitounet merged 10 commits from v2 into main 2025-01-10 20:39:09 +00:00
14 changed files with 122 additions and 233 deletions
Showing only changes of commit 89e2ccce02 - Show all commits

4
.env
View File

@ -23,8 +23,8 @@ APP_SECRET=
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
# #
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
DATABASE_URL="mysql://kumora:kumora@127.0.0.1:3309/kumora?serverVersion=8.0.32&charset=utf8mb4" # DATABASE_URL="mysql://kumora:kumora@127.0.0.1:3309/kumora?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" # DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ### ###< doctrine/doctrine-bundle ###

View File

View File

@ -1,15 +0,0 @@
services:
mysql:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: kumora
MYSQL_USER: kumora
MYSQL_PASSWORD: kumora
ports:
- "3309:3306"
volumes:
- mysql_datas_kumora:/var/lib/mysql
volumes:
mysql_datas_kumora:

View File

@ -1,33 +0,0 @@
<?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 Version20241228222158 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE `user` (id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', roles JSON NOT NULL, password VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, approuved TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_IDENTIFIER_USERNAME (username), UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE `user`');
$this->addSql('DROP TABLE messenger_messages');
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20241229133017 extends AbstractMigration
{
public function getDescription(): string
{
return 'Ajout de la table user et messenger_messages';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE "user" (id BLOB NOT NULL --(DC2Type:uuid)
, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
$this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
, available_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
, delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
)');
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE "user"');
$this->addSql('DROP TABLE messenger_messages');
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Command;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
#[AsCommand(
name: 'app:create-user',
description: 'Permet de créer un utilisateur',
)]
class CreateUserCommand extends Command
{
public function __construct(
private readonly UserPasswordHasherInterface $passwordHasher,
private readonly EntityManagerInterface $entityManager,
private readonly UserRepository $userRepository
)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$email = $io->ask('Email de l\'utilisateur');
$password = $io->askHidden('Mot de passe de l\'utilisateur');
$isAdmin = $io->confirm('Est-ce un administrateur ?');
try {
$user = $this->userRepository->findOneBy(['email' => $email]);
if ($user) {
$io->error('Un utilisateur existe déjà avec cet email');
return Command::FAILURE;
}
$user = new User();
$user->setEmail($email);
$user->setPassword($this->passwordHasher->hashPassword($user, $password));
$user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']);
$this->entityManager->persist($user);
$this->entityManager->flush();
$io->success('Utilisateur créé avec succès');
} catch (\Exception $e) {
$io->error('Une erreur est survenue lors de la création de l\'utilisateur');
}
return Command::SUCCESS;
}
}

View File

@ -1,46 +0,0 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Uid\Uuid;
class RegistrationController extends AbstractController
{
#[Route('/register', name: 'app_register')]
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
{
if ($this->isGranted('IS_AUTHENTICATED_FULLY')) {
return $this->redirectToRoute('app_home');
}
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var string $plainPassword */
$plainPassword = $form->get('plainPassword')->getData();
// encode the plain password
$user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword));
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash('success', 'Votre compte a été créé avec succès. Vous recevrez un email de confirmation une fois que votre compte aura été approuvé.');
return $this->redirectToRoute('app_login');
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form,
]);
}
}

View File

@ -11,15 +11,16 @@ use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')] #[ORM\Table(name: '`user`')]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
#[UniqueEntity(fields: ['email'], message: 'Un compte existe déjà avec cet email')] #[UniqueEntity(fields: ['email'], message: 'Un compte existe déjà avec cet email')]
#[UniqueEntity(fields: ['username'], message: 'Un compte existe déjà avec ce nom d\'utilisateur')]
class User implements UserInterface, PasswordAuthenticatedUserInterface class User implements UserInterface, PasswordAuthenticatedUserInterface
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\Column(type: 'uuid', length: 180)] #[ORM\Column(type: 'uuid', length: 180)]
private ?Uuid $id = null; private ?Uuid $id;
#[ORM\Column(length: 255)]
private ?string $email = null;
/** /**
* @var list<string> The user roles * @var list<string> The user roles
@ -33,15 +34,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column] #[ORM\Column]
private ?string $password = null; private ?string $password = null;
#[ORM\Column(length: 255)]
private ?string $username = null;
#[ORM\Column(length: 255)]
private ?string $email = null;
#[ORM\Column]
private ?bool $approuved = false;
public function __construct() public function __construct()
{ {
$this->id = Uuid::v4(); $this->id = Uuid::v4();
@ -110,18 +102,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
// $this->plainPassword = null; // $this->plainPassword = null;
} }
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): static
{
$this->username = $username;
return $this;
}
public function getEmail(): ?string public function getEmail(): ?string
{ {
return $this->email; return $this->email;
@ -133,16 +113,4 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this; return $this;
} }
public function isApprouved(): ?bool
{
return $this->approuved;
}
public function setApprouved(bool $approuved): static
{
$this->approuved = $approuved;
return $this;
}
} }

0
src/Form/.gitkeep Normal file
View File

View File

@ -1,54 +0,0 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username')
->add('email')
->add('plainPassword', PasswordType::class, [
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
])
->add('agreeTerms', CheckboxType::class, [
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Vous devez accepter les conditions d\'utilisation.',
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View File

@ -1,20 +1,15 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Hello HomeController!{% endblock %} {% block title %}Le cloud de Camélia-Studio{% endblock %}
{% block body %} {% block body %}
<style> <div class="min-h-96 flex items-center flex-col justify-center">
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } <h1 class="text-4xl font-bold mb-2 text-gray-900 dark:text-white">Bienvenue sur Kumora</h1>
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; } <p class="text-xl text-gray-600 dark:text-gray-300 mb-8">Notre espace de stockage en ligne, dédié aux membres de Camélia Studio, pour faciliter le partage de tous les fichiers et ressources de l'association.</p>
</style> <div class="mb-16">
<a href="{{ path('app_files_index') }}" class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:bg-gray-800 dark:text-white dark:border-gray-600 font-medium rounded-lg px-8 py-3">
<div class="example-wrapper"> Accéder à mon espace
<h1>Hello {{ controller_name }}! ✅</h1> </a>
</div>
This friendly message is coming from: </div>
<ul>
<li>Your controller at <code>/home/melaine/Coding/Camélia-Studio/Kumora/src/Controller/HomeController.php</code></li>
<li>Your template at <code>/home/melaine/Coding/Camélia-Studio/Kumora/templates/home/index.html.twig</code></li>
</ul>
</div>
{% endblock %} {% endblock %}

View File

@ -18,17 +18,16 @@
<li> <li>
<a href="{{ path('app_files_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Liste des fichiers</a> <a href="{{ path('app_files_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Liste des fichiers</a>
</li> </li>
<li>
<a href="{{ path('app_files_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
</li>
{% if not is_granted('IS_AUTHENTICATED_FULLY') %} {% if not is_granted('IS_AUTHENTICATED_FULLY') %}
<li> <li>
<a href="{{ path('app_login') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se connecter</a> <a href="{{ path('app_login') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se connecter</a>
</li> </li>
<li>
<a href="{{ path('app_register') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">S'inscrire</a>
</li>
{% else %} {% else %}
{% if is_granted('ROLE_ADMIN') %}
<li>
<a href="{{ path('app_files_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
</li>
{% endif %}
<li> <li>
<a href="{{ path('app_logout') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se déconnecter</a> <a href="{{ path('app_logout') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se déconnecter</a>
</li> </li>

View File

@ -1,24 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}Register{% endblock %}
{% block body %}
<div class="container mx-auto px-16 mt-4">
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">S'inscrire</h3>
{{ form_errors(registrationForm) }}
{{ form_start(registrationForm) }}
{{ form_row(registrationForm.username) }}
{{ form_row(registrationForm.email) }}
{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}
{{ form_row(registrationForm.agreeTerms) }}
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">S'inscrire</button>
{{ form_end(registrationForm) }}
</div>
</div>
{% endblock %}

View File

@ -23,7 +23,7 @@
</div> </div>
<div class="mb-5"> <div class="mb-5">
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Mot de passe</label> <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Mot de passe</label>
<input autocomplete="current-password" type="password" value="{{ last_username }}" name="_password" id="password" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required /> <input autocomplete="current-password" type="password" name="_password" id="password" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
</div> </div>
<input type="hidden" name="_csrf_token" data-controller="csrf-protection" <input type="hidden" name="_csrf_token" data-controller="csrf-protection"