From 6b39a0a015774cfa9258fc4c685b75c2d6008a9c Mon Sep 17 00:00:00 2001 From: user <user@imta.fr> Date: Tue, 25 Mar 2025 21:17:42 +0100 Subject: [PATCH] User V2 --- .env | 2 + config/packages/security.yaml | 6 +- src/Controller/AdminController.php | 14 ++++- src/Controller/RegistrationController.php | 40 +++++++++++++ src/Controller/SecurityController.php | 33 +++++++++++ src/Controller/UserController.php | 1 + src/Entity/User.php | 38 ++++++++++-- src/Form/RegistrationFormType.php | 70 +++++++++++++++++++++++ src/Repository/ItemRepository.php | 9 +++ src/Repository/UserRepository.php | 10 ++++ src/Repository/WishlistRepository.php | 2 +- templates/base.html.twig | 20 +++---- templates/registration/index.html.twig | 20 +++++++ templates/registration/register.html.twig | 18 ++++++ templates/security/index.html.twig | 20 +++++++ templates/security/login.html.twig | 29 ++++++++++ 16 files changed, 310 insertions(+), 22 deletions(-) create mode 100644 src/Controller/RegistrationController.php create mode 100644 src/Controller/SecurityController.php create mode 100644 src/Form/RegistrationFormType.php create mode 100644 templates/registration/index.html.twig create mode 100644 templates/registration/register.html.twig create mode 100644 templates/security/index.html.twig create mode 100644 templates/security/login.html.twig diff --git a/.env b/.env index 89db3161..f52592bc 100644 --- a/.env +++ b/.env @@ -29,4 +29,6 @@ MESSENGER_TRANSPORT_DSN=sync:// # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" DATABASE_URL="mysql://root:root@172.18.0.3:3306/wishlist?serverVersion=8.0" ###< doctrine/doctrine-bundle ### +APP_SECRET=3d9f431b0fd768e0cb2e8b139c66e9fd67ecbd49762f6a345d459e2f115f61b4 + diff --git a/config/packages/security.yaml b/config/packages/security.yaml index ab531b70..c1cc3ab4 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -24,9 +24,9 @@ security: remember_me: secret: '%kernel.secret%' - access_control: - - { path: ^/admin, allow_if: "user and user.isAdmin == true" } - - { path: ^/locked, allow_if: "user and user.isLocked == true" } + # access_control: + # - { path: ^/admin, allow_if: "user and user.isAdmin == true" } + # - { path: ^/locked, allow_if: "user and user.isLocked == true" } when@test: security: diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index 6c80d9af..f214274c 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -5,6 +5,10 @@ namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Doctrine\ORM\EntityManagerInterface; + +use App\Entity\Item; +use App\Entity\Wishlist; final class AdminController extends AbstractController { @@ -19,9 +23,17 @@ final class AdminController extends AbstractController #[Route('/admin/dashboard', name: 'admin_dashboard')] public function dashboard(EntityManagerInterface $entityManager): Response { - $topItems = $entityManager->getRepository(WishlistItem::class)->findTopExpensiveItems(); + $topItems = $entityManager->getRepository(Item::class)->findTopExpensiveItems(); $topWishlists = $entityManager->getRepository(Wishlist::class)->findTopWishlistsByValue(); + + if (!$topItems) { + $topItems = []; + } + + if (!$topWishlists) { + $topWishlists = []; + } return $this->render('admin/dashboard.html.twig', [ 'topItems' => $topItems, 'topWishlists' => $topWishlists, diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 00000000..fbf5028d --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,40 @@ +<?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\Annotation\Route; + +class RegistrationController extends AbstractController +{ + #[Route('/register', name: 'register')] + public function register(Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $entityManager): Response + { + $user = new User(); + $form = $this->createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // Hacher le mot de passe + $hashedPassword = $passwordHasher->hashPassword($user, $user->getPassword()); + $user->setPassword($hashedPassword); + + // Sauvegarder l'utilisateur + $entityManager->persist($user); + $entityManager->flush(); + + // Rediriger vers la page de connexion + return $this->redirectToRoute('login'); + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form->createView(), + ]); + } +} \ No newline at end of file diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 00000000..af98a789 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,33 @@ +<?php + +namespace App\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Doctrine\ORM\EntityManagerInterface; + +class SecurityController extends AbstractController +{ + #[Route('/login', name: 'login')] + public function login(AuthenticationUtils $authenticationUtils): Response + { + // Récupère les erreurs de connexion, s'il y en a + $error = $authenticationUtils->getLastAuthenticationError(); + + // Récupère le dernier email saisi par l'utilisateur + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('security/login.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } + + #[Route('/logout', name: 'logout')] + public function logout(): void + { + // Ce contrôleur peut rester vide, Symfony gère la déconnexion automatiquement + } +} \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 97f6ded8..b058aa94 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -5,6 +5,7 @@ namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Doctrine\ORM\EntityManagerInterface; final class UserController extends AbstractController { diff --git a/src/Entity/User.php b/src/Entity/User.php index 9ba7e129..197c8767 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -5,6 +5,8 @@ namespace App\Entity; use App\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; #[ORM\Entity(repositoryClass: UserRepository::class)] class User implements UserInterface @@ -53,6 +55,20 @@ class User implements UserInterface #[ORM\Column(length: 255, nullable: true)] private ?string $image = null; + #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Wishlist::class)] + private Collection $wishlists; + + #[ORM\OneToMany(mappedBy: 'invitedUser', targetEntity: Item::class)] + private Collection $invitations; + + public function __construct() + { + $this->wishlists = new ArrayCollection(); + $this->invitations = new ArrayCollection(); + $this->roles = ['ROLE_USER']; + $this->isLocked = false; + } + public function getId(): ?int { return $this->id; @@ -142,11 +158,23 @@ class User implements UserInterface return $this; } - public function __construct() + public function getWishlists(): Collection { - $this->wishlists = new ArrayCollection(); - $this->purchasedItems = new ArrayCollection(); - $this->roles = ['ROLE_USER']; - $this->isLocked = false; + return $this->wishlists; } + + // public function getInvitations(): Collection + // { + // return $this->invitations; + // } + + // public function addInvitation(Item $invitation): static + // { + // if (!$this->invitations->contains($invitation)) { + // $this->invitations[] = $invitation; + // $invitation->setInvitedUser($this); + // } + + // } + } diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php new file mode 100644 index 00000000..89c463ad --- /dev/null +++ b/src/Form/RegistrationFormType.php @@ -0,0 +1,70 @@ +<?php + +namespace App\Form; + +use App\Entity\User; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\File; +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('firstName', TextType::class, [ + 'label' => 'Prénom', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('lastName', TextType::class, [ + 'label' => 'Nom', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('email', EmailType::class, [ + 'label' => 'Email', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('password', PasswordType::class, [ + 'label' => 'Mot de passe', + 'constraints' => [ + new NotBlank(), + new Length(['min' => 6]), + ], + ]) + ->add('image', FileType::class, [ + 'label' => 'Image de profil (optionnel)', + 'required' => false, + 'mapped' => false, + 'constraints' => [ + new File([ + 'maxSize' => '2M', + 'mimeTypes' => [ + 'image/jpeg', + 'image/png', + ], + 'mimeTypesMessage' => 'Veuillez télécharger une image valide (JPEG ou PNG).', + ]), + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} \ No newline at end of file diff --git a/src/Repository/ItemRepository.php b/src/Repository/ItemRepository.php index 229f4065..1d57e045 100644 --- a/src/Repository/ItemRepository.php +++ b/src/Repository/ItemRepository.php @@ -16,6 +16,15 @@ class ItemRepository extends ServiceEntityRepository parent::__construct($registry, Item::class); } + + public function findTopExpensiveItems(): array + { + return $this->createQueryBuilder('i') // Alias "i" pour Item + ->orderBy('i.price', 'DESC') // Trier par prix décroissant + ->setMaxResults(3) // Limiter à 3 résultats + ->getQuery() + ->getResult(); // Exécuter la requête + } // /** // * @return Item[] Returns an array of Item objects // */ diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 96ef606b..58793646 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -32,6 +32,16 @@ public function findLockedUsers(): array ->getQuery() ->getResult(); } + +public function findByEmail(string $email): ?User +{ + return $this->createQueryBuilder('u') + ->where('u.email = :email') + ->setParameter('email', $email) + ->getQuery() + ->getOneOrNullResult(); +} + // /** // * @return User[] Returns an array of User objects // */ diff --git a/src/Repository/WishlistRepository.php b/src/Repository/WishlistRepository.php index ed438367..e714b9fd 100644 --- a/src/Repository/WishlistRepository.php +++ b/src/Repository/WishlistRepository.php @@ -60,7 +60,7 @@ class WishlistRepository extends ServiceEntityRepository public function findTopWishlistsByValue(): array { return $this->createQueryBuilder('u') - ->join('u.wishlists', 'w') + ->join('u.wishlist', 'w') ->join('w.items', 'i') ->where('i.isPurchased = true') ->groupBy('w.id') diff --git a/templates/base.html.twig b/templates/base.html.twig index 4b5725c9..213ebaff 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -6,10 +6,16 @@ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>"> <link rel="stylesheet" href="{{ asset('css/style.css') }}"> {% block stylesheets %} + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> + <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet"> + <link href="{{ asset('css/style.css') }}" rel="stylesheet"> {% endblock %} {% block javascripts %} - {% block importmap %}{{ importmap('app') }}{% endblock %} + {% block importmap %}{{ importmap('app') }} + {% endblock %} + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/js/all.min.js"></script> {% endblock %} </head> <body> @@ -22,17 +28,7 @@ {% endfor %} {% endfor %} - {% block stylesheets %} - <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> - <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet"> - <link href="{{ asset('css/style.css') }}" rel="stylesheet"> - {% endblock %} - - {% block javascripts %} - <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/js/all.min.js"></script> - {% endblock %} - + {% block body %}{% endblock %} </body> </html> diff --git a/templates/registration/index.html.twig b/templates/registration/index.html.twig new file mode 100644 index 00000000..e7ded79b --- /dev/null +++ b/templates/registration/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello RegistrationController!{% endblock %} + +{% block body %} +<style> + .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } + .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } +</style> + +<div class="example-wrapper"> + <h1>Hello {{ controller_name }}! ✅</h1> + + This friendly message is coming from: + <ul> + <li>Your controller at <code>/home/user/www/Exercices/Wishlist-application/src/Controller/RegistrationController.php</code></li> + <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/registration/index.html.twig</code></li> + </ul> +</div> +{% endblock %} diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig new file mode 100644 index 00000000..1f9833da --- /dev/null +++ b/templates/registration/register.html.twig @@ -0,0 +1,18 @@ +{% extends 'base.html.twig' %} + +{% block title %}Inscription{% endblock %} + +{% block body %} +<div class="container mt-5"> + <h1>Inscription</h1> + + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.firstName) }} + {{ form_row(registrationForm.lastName) }} + {{ form_row(registrationForm.email) }} + {{ form_row(registrationForm.password) }} + {{ form_row(registrationForm.image) }} + <button type="submit" class="btn btn-primary">S'inscrire</button> + {{ form_end(registrationForm) }} +</div> +{% endblock %} \ No newline at end of file diff --git a/templates/security/index.html.twig b/templates/security/index.html.twig new file mode 100644 index 00000000..71358c2f --- /dev/null +++ b/templates/security/index.html.twig @@ -0,0 +1,20 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello SecurityController!{% endblock %} + +{% block body %} +<style> + .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } + .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } +</style> + +<div class="example-wrapper"> + <h1>Hello {{ controller_name }}! ✅</h1> + + This friendly message is coming from: + <ul> + <li>Your controller at <code>/home/user/www/Exercices/Wishlist-application/src/Controller/SecurityController.php</code></li> + <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/security/index.html.twig</code></li> + </ul> +</div> +{% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 00000000..244b6d55 --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,29 @@ +{% extends 'base.html.twig' %} + +{% block title %}Connexion{% endblock %} + +{% block body %} +<div class="container mt-5"> + <h1>Connexion</h1> + + {% if error %} + <div class="alert alert-danger"> + {{ error.messageKey|trans(error.messageData, 'security') }} + </div> + {% endif %} + + <form method="post" action="{{ path('login') }}"> + <div class="mb-3"> + <label for="username" class="form-label">Email</label> + <input type="text" name="_username" id="username" class="form-control" value="{{ last_username }}" required autofocus> + </div> + + <div class="mb-3"> + <label for="password" class="form-label">Mot de passe</label> + <input type="password" name="_password" id="password" class="form-control" required> + </div> + + <button type="submit" class="btn btn-primary">Se connecter</button> + </form> +</div> +{% endblock %} \ No newline at end of file -- GitLab