From 30773ddfd74b4737567bb2342b1a6789f4f417d4 Mon Sep 17 00:00:00 2001
From: user <user@imta.fr>
Date: Tue, 25 Mar 2025 18:57:18 +0100
Subject: [PATCH] User V1

---
 config/packages/security.yaml         |  41 ++++---
 config/routes/security.yaml           |   2 +-
 public/css/style.css                  |  39 +++++++
 src/Controller/AdminController.php    |  68 ++++++++++++
 src/Controller/UserController.php     |  64 +++++++++++
 src/Entity/User.php                   | 152 ++++++++++++++++++++++++++
 src/Repository/UserRepository.php     |  59 ++++++++++
 src/Repository/WishlistRepository.php |  12 ++
 templates/admin/dashboard.html.twig   | 118 ++++++++++++++++++++
 templates/admin/index.html.twig       |  20 ++++
 templates/base.html.twig              |  20 ++++
 templates/user/index.html.twig        |  20 ++++
 12 files changed, 593 insertions(+), 22 deletions(-)
 create mode 100644 src/Controller/AdminController.php
 create mode 100644 src/Controller/UserController.php
 create mode 100644 src/Entity/User.php
 create mode 100644 src/Repository/UserRepository.php
 create mode 100644 templates/admin/dashboard.html.twig
 create mode 100644 templates/admin/index.html.twig
 create mode 100644 templates/user/index.html.twig

diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index 367af25a..ab531b70 100644
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -1,39 +1,38 @@
 security:
-    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
     password_hashers:
         Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
-    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
+
     providers:
-        users_in_memory: { memory: null }
+        app_user_provider:
+            entity:
+                class: App\Entity\User
+                property: email  # Ou 'username' si tu utilises un autre identifiant
+
     firewalls:
         dev:
             pattern: ^/(_(profiler|wdt)|css|images|js)/
             security: false
+
         main:
             lazy: true
-            provider: users_in_memory
-
-            # activate different ways to authenticate
-            # https://symfony.com/doc/current/security.html#the-firewall
-
-            # https://symfony.com/doc/current/security/impersonating_user.html
-            # switch_user: true
+            provider: app_user_provider
+            form_login:
+                login_path: login
+                check_path: login
+            logout:
+                path: logout
+            remember_me:
+                secret: '%kernel.secret%'
 
-    # Easy way to control access for large sections of your site
-    # Note: Only the *first* access control that matches will be used
     access_control:
-        # - { path: ^/admin, roles: ROLE_ADMIN }
-        # - { path: ^/profile, roles: ROLE_USER }
+        - { path: ^/admin, allow_if: "user and user.isAdmin == true" }
+        - { path: ^/locked, allow_if: "user and user.isLocked == true" }
 
 when@test:
     security:
         password_hashers:
-            # By default, password hashers are resource intensive and take time. This is
-            # important to generate secure password hashes. In tests however, secure hashes
-            # are not important, waste resources and increase test times. The following
-            # reduces the work factor to the lowest possible values.
             Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
                 algorithm: auto
-                cost: 4 # Lowest possible value for bcrypt
-                time_cost: 3 # Lowest possible value for argon
-                memory_cost: 10 # Lowest possible value for argon
+                cost: 4
+                time_cost: 3
+                memory_cost: 10
diff --git a/config/routes/security.yaml b/config/routes/security.yaml
index f853be15..60a7afbd 100644
--- a/config/routes/security.yaml
+++ b/config/routes/security.yaml
@@ -1,3 +1,3 @@
 _security_logout:
     resource: security.route_loader.logout
-    type: service
+    type: service
\ No newline at end of file
diff --git a/public/css/style.css b/public/css/style.css
index d99f15ed..31d7b132 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -287,4 +287,43 @@ h1 {
     border-radius: 3px;
     padding: 5px;
     cursor: pointer;
+}
+
+/* Couleurs IMT Atlantique */
+:root {
+    --imt-primary: #00326E;
+    --imt-secondary: #009FE3;
+    --imt-accent: #96C11F;
+}
+
+/* Styles généraux */
+body {
+    background-color: #f8f9fa;
+}
+
+/* En-tête */
+.navbar {
+    background-color: var(--imt-primary);
+}
+
+/* Boutons */
+.btn-primary {
+    background-color: var(--imt-primary);
+    border-color: var(--imt-primary);
+}
+
+.btn-secondary {
+    background-color: var(--imt-secondary);
+    border-color: var(--imt-secondary);
+}
+
+/* Cards */
+.card {
+    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
+}
+
+/* Tables */
+.table thead th {
+    background-color: var(--imt-primary);
+    color: white;
 }
\ No newline at end of file
diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php
new file mode 100644
index 00000000..6c80d9af
--- /dev/null
+++ b/src/Controller/AdminController.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace App\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Attribute\Route;
+
+final class AdminController extends AbstractController
+{
+    #[Route('/admin', name: 'app_admin')]
+    public function index(): Response
+    {
+        return $this->render('admin/index.html.twig', [
+            'controller_name' => 'AdminController',
+        ]);
+    }
+
+    #[Route('/admin/dashboard', name: 'admin_dashboard')]
+    public function dashboard(EntityManagerInterface $entityManager): Response
+    {
+        $topItems = $entityManager->getRepository(WishlistItem::class)->findTopExpensiveItems();
+        $topWishlists = $entityManager->getRepository(Wishlist::class)->findTopWishlistsByValue();
+
+        return $this->render('admin/dashboard.html.twig', [
+            'topItems' => $topItems,
+            'topWishlists' => $topWishlists,
+        ]);
+    }
+
+    #[Route('/admin/users', name: 'admin_users')]
+    public function manageUsers(EntityManagerInterface $entityManager): Response
+    {
+        $users = $entityManager->getRepository(User::class)->findAll();
+
+        return $this->render('admin/users.html.twig', [
+            'users' => $users,
+        ]);
+    }
+
+    #[Route('/admin/user/{id}/lock', name: 'admin_user_lock', methods: ['POST'])]
+    public function lockUser(User $user, EntityManagerInterface $entityManager): Response
+    {
+        $user->setIsLocked(true);
+        $entityManager->flush();
+
+        return $this->redirectToRoute('admin_users');
+    }
+
+    #[Route('/admin/user/{id}/unlock', name: 'admin_user_unlock', methods: ['POST'])]
+    public function unlockUser(User $user, EntityManagerInterface $entityManager): Response
+    {
+        $user->setIsLocked(false);
+        $entityManager->flush();
+
+        return $this->redirectToRoute('admin_users');
+    }
+
+    #[Route('/admin/user/{id}/delete', name: 'admin_user_delete', methods: ['POST'])]
+    public function deleteUser(User $user, EntityManagerInterface $entityManager): Response
+    {
+        $entityManager->remove($user);
+        $entityManager->flush();
+
+        return $this->redirectToRoute('admin_users');
+    }
+
+}
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
new file mode 100644
index 00000000..97f6ded8
--- /dev/null
+++ b/src/Controller/UserController.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Attribute\Route;
+
+final class UserController extends AbstractController
+{
+    #[Route('/user', name: 'app_user')]
+    public function index(): Response
+    {
+        return $this->render('user/index.html.twig', [
+            'controller_name' => 'UserController',
+        ]);
+    }
+
+
+    #[Route('/user/dashboard', name: 'user_dashboard')]
+    public function dashboard(EntityManagerInterface $entityManager): Response
+    {
+        $user = $this->getUser();
+        $wishlists = $entityManager->getRepository(Wishlist::class)->findBy(['owner' => $user]);
+
+        return $this->render('user/dashboard.html.twig', [
+            'user' => $user,
+            'wishlists' => $wishlists,
+        ]);
+    }
+
+    #[Route('/user/wishlist/new', name: 'user_wishlist_new', methods: ['GET', 'POST'])]
+    public function createWishlist(Request $request, EntityManagerInterface $entityManager): Response
+    {
+        $wishlist = new Wishlist();
+        $form = $this->createForm(WishlistType::class, $wishlist);
+        $form->handleRequest($request);
+
+        if ($form->isSubmitted() && $form->isValid()) {
+            $wishlist->setOwner($this->getUser());
+            $entityManager->persist($wishlist);
+            $entityManager->flush();
+
+            $this->addFlash('success', 'Wishlist créée avec succès.');
+            return $this->redirectToRoute('user_dashboard');
+        }
+
+        return $this->render('user/wishlist_form.html.twig', [
+            'form' => $form->createView(),
+        ]);
+    }
+
+    #[Route('/user/wishlist/{id}/delete', name: 'user_wishlist_delete', methods: ['POST'])]
+    public function deleteWishlist(Wishlist $wishlist, EntityManagerInterface $entityManager): Response
+    {
+        $this->denyAccessUnlessGranted('delete', $wishlist);
+
+        $entityManager->remove($wishlist);
+        $entityManager->flush();
+
+        $this->addFlash('success', 'Wishlist supprimée avec succès.');
+        return $this->redirectToRoute('user_dashboard');
+    }
+}
diff --git a/src/Entity/User.php b/src/Entity/User.php
new file mode 100644
index 00000000..9ba7e129
--- /dev/null
+++ b/src/Entity/User.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace App\Entity;
+
+use App\Repository\UserRepository;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+#[ORM\Entity(repositoryClass: UserRepository::class)]
+class User implements UserInterface
+{
+    private array $roles = [];
+
+    public function getRoles(): array
+    {
+        return $this->roles;
+    }
+
+    public function getUserIdentifier(): string
+    {
+        return $this->email;
+    }
+
+    public function eraseCredentials(): void
+    {
+        // If you store any temporary, sensitive data on the user, clear it here
+        // $this->plainPassword = null;
+    }
+
+    #[ORM\Id]
+    #[ORM\GeneratedValue]
+    #[ORM\Column]
+    private ?int $id = null;
+
+    #[ORM\Column(length: 255)]
+    private ?string $email = null;
+
+    #[ORM\Column(length: 63)]
+    private ?string $firstName = null;
+
+    #[ORM\Column(length: 63)]
+    private ?string $lastName = null;
+
+    #[ORM\Column(length: 255)]
+    private ?string $password = null;
+
+    #[ORM\Column]
+    private ?bool $isLocked = null;
+
+    #[ORM\Column]
+    private ?bool $isAdmin = null;
+
+    #[ORM\Column(length: 255, nullable: true)]
+    private ?string $image = null;
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function getEmail(): ?string
+    {
+        return $this->email;
+    }
+
+    public function setEmail(string $email): static
+    {
+        $this->email = $email;
+
+        return $this;
+    }
+
+    public function getFirstName(): ?string
+    {
+        return $this->firstName;
+    }
+
+    public function setFirstName(string $firstName): static
+    {
+        $this->firstName = $firstName;
+
+        return $this;
+    }
+
+    public function getLastName(): ?string
+    {
+        return $this->lastName;
+    }
+
+    public function setLastName(string $lastName): static
+    {
+        $this->lastName = $lastName;
+
+        return $this;
+    }
+
+    public function getPassword(): ?string
+    {
+        return $this->password;
+    }
+
+    public function setPassword(string $password): static
+    {
+        $this->password = $password;
+
+        return $this;
+    }
+
+    public function isLocked(): ?bool
+    {
+        return $this->isLocked;
+    }
+
+    public function setIsLocked(bool $isLocked): static
+    {
+        $this->isLocked = $isLocked;
+
+        return $this;
+    }
+
+    public function isAdmin(): ?bool
+    {
+        return $this->isAdmin;
+    }
+
+    public function setIsAdmin(bool $isAdmin): static
+    {
+        $this->isAdmin = $isAdmin;
+
+        return $this;
+    }
+
+    public function getImage(): ?string
+    {
+        return $this->image;
+    }
+
+    public function setImage(?string $image): static
+    {
+        $this->image = $image;
+
+        return $this;
+    }
+
+    public function __construct()
+    {
+        $this->wishlists = new ArrayCollection();
+        $this->purchasedItems = new ArrayCollection();
+        $this->roles = ['ROLE_USER'];
+        $this->isLocked = false;
+    }
+}
diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php
new file mode 100644
index 00000000..96ef606b
--- /dev/null
+++ b/src/Repository/UserRepository.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Repository;
+
+use App\Entity\User;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+/**
+ * @extends ServiceEntityRepository<User>
+ */
+class UserRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, User::class);
+    }
+
+    public function findAllUsers(): array
+{
+    return $this->createQueryBuilder('u')
+        ->orderBy('u.username', 'ASC')
+        ->getQuery()
+        ->getResult();
+}
+
+public function findLockedUsers(): array
+{
+    return $this->createQueryBuilder('u')
+        ->where('u.isLocked = :locked')
+        ->setParameter('locked', true)
+        ->getQuery()
+        ->getResult();
+}
+    //    /**
+    //     * @return User[] Returns an array of User objects
+    //     */
+    //    public function findByExampleField($value): array
+    //    {
+    //        return $this->createQueryBuilder('u')
+    //            ->andWhere('u.exampleField = :val')
+    //            ->setParameter('val', $value)
+    //            ->orderBy('u.id', 'ASC')
+    //            ->setMaxResults(10)
+    //            ->getQuery()
+    //            ->getResult()
+    //        ;
+    //    }
+
+    //    public function findOneBySomeField($value): ?User
+    //    {
+    //        return $this->createQueryBuilder('u')
+    //            ->andWhere('u.exampleField = :val')
+    //            ->setParameter('val', $value)
+    //            ->getQuery()
+    //            ->getOneOrNullResult()
+    //        ;
+    //    }
+}
diff --git a/src/Repository/WishlistRepository.php b/src/Repository/WishlistRepository.php
index 543e6355..24833563 100644
--- a/src/Repository/WishlistRepository.php
+++ b/src/Repository/WishlistRepository.php
@@ -16,6 +16,18 @@ class WishlistRepository extends ServiceEntityRepository
         parent::__construct($registry, Wishlist::class);
     }
     
+    public function findTopWishlistsByValue(): array
+    {   
+        return $this->createQueryBuilder('u')
+            ->join('u.wishlists', 'w')
+            ->join('w.items', 'i')
+            ->where('i.isPurchased = true')
+            ->groupBy('w.id')
+            ->orderBy('SUM(i.price)', 'DESC')
+            ->setMaxResults(3)
+            ->getQuery()
+            ->getResult();
+    }
     //    /**
     //     * @return Wishlist[] Returns an array of Wishlist objects
     //     */
diff --git a/templates/admin/dashboard.html.twig b/templates/admin/dashboard.html.twig
new file mode 100644
index 00000000..8c663bf6
--- /dev/null
+++ b/templates/admin/dashboard.html.twig
@@ -0,0 +1,118 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Administration{% endblock %}
+
+{% block body %}
+<div class="container mt-4">
+    <h1>Dashboard Administrateur</h1>
+
+    <div class="row mt-4">
+        <!-- Statistiques -->
+        <div class="col-md-6">
+            <div class="card">
+                <div class="card-body">
+                    <h5 class="card-title">Top 3 des items les plus chers</h5>
+                    {% if topItems is empty %}
+                        <p class="text-muted">Aucun item trouvé</p>
+                    {% else %}
+                        <ul class="list-group">
+                        {% for item in topItems %}
+                            <li class="list-group-item d-flex justify-content-between align-items-center">
+                                {{ item.title }}
+                                <span class="badge bg-primary rounded-pill">{{ item.price }} €</span>
+                            </li>
+                        {% endfor %}
+                        </ul>
+                    {% endif %}
+                </div>
+            </div>
+        </div>
+
+        <div class="col-md-6">
+            <div class="card">
+                <div class="card-body">
+                    <h5 class="card-title">Top 3 des wishlists par valeur</h5>
+                    {% if topWishlists is empty %}
+                        <p class="text-muted">Aucune wishlist trouvée</p>
+                    {% else %}
+                        <ul class="list-group">
+                        {% for wishlist in topWishlists %}
+                            <li class="list-group-item d-flex justify-content-between align-items-center">
+                                {{ wishlist.name }}
+                                <span class="badge bg-success rounded-pill">{{ wishlist.totalValue }} €</span>
+                            </li>
+                        {% endfor %}
+                        </ul>
+                    {% endif %}
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- Gestion des utilisateurs -->
+    <div class="card mt-4">
+        <div class="card-body">
+            <h5 class="card-title">Gestion des utilisateurs</h5>
+            
+            {% if users is empty %}
+                <p class="text-muted">Aucun utilisateur enregistré</p>
+            {% else %}
+                <div class="table-responsive">
+                    <table class="table">
+                        <thead>
+                            <tr>
+                                <th>ID</th>
+                                <th>Nom d'utilisateur</th>
+                                <th>Email</th>
+                                <th>Statut</th>
+                                <th>Actions</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {% for user in users %}
+                                <tr>
+                                    <td>{{ user.id }}</td>
+                                    <td>{{ user.username }}</td>
+                                    <td>{{ user.email }}</td>
+                                    <td>
+                                        {% if user.isLocked %}
+                                            <span class="badge bg-danger">Verrouillé</span>
+                                        {% else %}
+                                            <span class="badge bg-success">Actif</span>
+                                        {% endif %}
+                                    </td>
+                                    <td>
+                                        <div class="btn-group">
+                                            {% if user.isLocked %}
+                                                <form method="post" action="{{ path('admin_user_unlock', {'id': user.id}) }}" style="display: inline;">
+                                                    <button type="submit" class="btn btn-sm btn-success">
+                                                        <i class="fas fa-unlock"></i>
+                                                    </button>
+                                                </form>
+                                            {% else %}
+                                                <form method="post" action="{{ path('admin_user_lock', {'id': user.id}) }}" style="display: inline;">
+                                                    <button type="submit" class="btn btn-sm btn-warning">
+                                                        <i class="fas fa-lock"></i>
+                                                    </button>
+                                                </form>
+                                            {% endif %}
+                                            <form method="post" 
+                                                  action="{{ path('admin_user_delete', {'id': user.id}) }}" 
+                                                  onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ?');"
+                                                  style="display: inline;">
+                                                <button type="submit" class="btn btn-sm btn-danger">
+                                                    <i class="fas fa-trash"></i>
+                                                </button>
+                                            </form>
+                                        </div>
+                                    </td>
+                                </tr>
+                            {% endfor %}
+                        </tbody>
+                    </table>
+                </div>
+            {% endif %}
+        </div>
+    </div>
+</div>
+{% endblock %}
\ No newline at end of file
diff --git a/templates/admin/index.html.twig b/templates/admin/index.html.twig
new file mode 100644
index 00000000..5e68ff4e
--- /dev/null
+++ b/templates/admin/index.html.twig
@@ -0,0 +1,20 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Hello AdminController!{% 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/AdminController.php</code></li>
+        <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/admin/index.html.twig</code></li>
+    </ul>
+</div>
+{% endblock %}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index 2eac7d55..4b5725c9 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -13,6 +13,26 @@
         {% endblock %}
     </head>
     <body>
+    {% for label, messages in app.flashes %}
+    {% for message in messages %}
+        <div class="alert alert-{{ label }} alert-dismissible fade show" role="alert">
+            {{ message }}
+            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+        </div>
+    {% 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/user/index.html.twig b/templates/user/index.html.twig
new file mode 100644
index 00000000..bea6b090
--- /dev/null
+++ b/templates/user/index.html.twig
@@ -0,0 +1,20 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Hello UserController!{% 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/UserController.php</code></li>
+        <li>Your template at <code>/home/user/www/Exercices/Wishlist-application/templates/user/index.html.twig</code></li>
+    </ul>
+</div>
+{% endblock %}
-- 
GitLab