diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 7e1ee1f1e0d1183963de0d822c064178a2c4422c..8f51215d135b9204537fc169472a59bcc545acd6 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -5,6 +5,7 @@ framework: # Note that the session will be started ONLY if you read or write from it. session: true + #esi: true #fragments: true @@ -13,3 +14,5 @@ when@test: test: true session: storage_factory_id: session.storage.factory.mock_file + + diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 6910e77dfff51a77345d976573e08d0cb94fd825..eaa621891cb5c4f5c081739fe9633aa4af503629 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -25,19 +25,12 @@ security: secret: '%kernel.secret%' access_control: - # Allow access to /login and / without being authenticated - - { path: ^/login, allow_if: "1" } - - { path: ^/register, allow_if: "1" } - - { path: ^/$, allow_if: "1" } - - # Allow users who are admins to access the /admin path - - { path: ^/admin, allow_if: "user and user.isAdmin() == true" } - - # Allow users who are not locked to access other pages - - { path: ^/.*, allow_if: "user and user.isLocked() != true" } - - - + - { path: ^/login, allow_if: "1" } + - { path: ^/register, allow_if: "1" } + - { path: ^/$, allow_if: "1" } # Page d'accueil accessible à tous + - { path: ^/admin, allow_if: "user and user.isAdmin() == true" } + - { path: ^/, allow_if: "user" } + when@test: security: password_hashers: diff --git a/init.sql b/init.sql new file mode 100644 index 0000000000000000000000000000000000000000..72ab8036f98d3d4a79e4b12b59f9430fdc408565 --- /dev/null +++ b/init.sql @@ -0,0 +1,27 @@ +-- Start transaction +START TRANSACTION; + +-- Insert users (one admin and one non-admin) +INSERT INTO `user` (`id`, `email`, `first_name`, `last_name`, `password`, `is_locked`, `is_admin`, `image`) VALUES +(1, 'admin@example.com', 'Admin', 'User', '$2y$10$adminpasswordhash', 0, 1, NULL), -- Admin user +(2, 'user@example.com', 'Regular', 'User', '$2y$10$userpasswordhash', 0, 0, NULL); -- Non-admin user + +-- Insert wishlists (one for each user) +INSERT INTO `wishlist` (`id`, `name`, `deadline`, `is_disabled`, `owner_id`) VALUES +(1, 'Admin Wishlist', '2025-12-31 23:59:59', 0, 1), -- Admin's wishlist +(2, 'User Wishlist', '2025-12-31 23:59:59', 0, 2); -- Non-admin's wishlist + +-- Insert items (linked to wishlists) +INSERT INTO `item` (`id`, `wishlist_id`, `title`, `description`, `url`, `image`, `price`) VALUES +(1, 1, 'Admin Item 1', 'Description for Admin Item 1', 'https://example.com/admin-item-1', NULL, 100.00), -- Item for admin's wishlist +(2, 1, 'Admin Item 2', 'Description for Admin Item 2', 'https://example.com/admin-item-2', NULL, 200.00), -- Item for admin's wishlist +(3, 2, 'User Item 1', 'Description for User Item 1', 'https://example.com/user-item-1', NULL, 50.00), -- Item for useVALUESr's wishlist +(4, 2, 'User Item 2', 'Description for User Item 2', 'https://example.com/user-item-2', NULL, 75.00); -- Item for user's wishlist + +-- Insert purchase proofs (linked to items) +INSERT INTO `purchase_proof` (`id`, `item_id`, `buyer_id`, `congrats_text`, `image_path`) VALUES +(1, 1, 1, 'Congrats on purchasing Admin Item 1!', '/path/to/admin-item-1-proof.png'), -- Proof for admin's item, buyer is user with ID 2 +(2, 3, 1, 'Congrats on purchasing User Item 1!', '/path/to/user-item-1-proof.png'); -- Proof for user's item, buyer is user with ID 1 + +-- Commit transaction +COMMIT; \ No newline at end of file diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index c5ec300071cddce29cff4e7fc278732b5d119c0c..af3ddc1e13bb222529633a8b4444c7ed44ca7ce9 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -33,7 +33,6 @@ class HomeController extends AbstractController $links['My Wishlists'] = $this->generateUrl('app_wishlist_index'); $links['Profile'] = $this->generateUrl('user_profile'); $links['Logout'] = $this->generateUrl('logout'); - dump($user->getId()); $links['See my purchase proofs'] = $this->generateUrl('user_purchase_proofs'); diff --git a/src/Controller/PurchaseProofController.php b/src/Controller/PurchaseProofController.php index b31218ca477fe2796c7a6f2ed10ec3a68d090783..935dc5f36a8de9061647f7ab895b6f327a7ea270 100644 --- a/src/Controller/PurchaseProofController.php +++ b/src/Controller/PurchaseProofController.php @@ -47,4 +47,6 @@ class PurchaseProofController extends AbstractController 'item' => $item, ]); } + + } diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index 84f8c732dcb1eb6f3430e4c0a6f8d11973f332d6..d2259d0b9e72eea820c889faa5f2920235e3f54c 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -32,6 +32,9 @@ class RegistrationController extends AbstractController // Rediriger vers la page de connexion return $this->redirectToRoute('login'); } + else { + // Si le formulaire n'est pas valide, les erreurs seront disponibles dans la vue + } return $this->render('registration/register.html.twig', [ 'registrationForm' => $form->createView(), diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index f8088efc5d4c1a61f96b7e27c7d4dda6d1812078..411f3e0493822f1762c2810a786d006cfa61482f 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -9,6 +9,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\HttpFoundation\Request; +use App\Repository\PurchaseProofRepository; use App\Form\UserType; @@ -103,4 +104,27 @@ final class UserController extends AbstractController 'form' => $form->createView(), ]); } -} + + #[Route('/user/purchase-proofs', name: 'user_purchase_proofs')] + public function listPurchaseProofs(PurchaseProofRepository $purchaseProofRepository): Response + { + $user = $this->getUser(); + if (!$user) { + throw $this->createAccessDeniedException('You must be logged in to access this page.'); + } + + $purchaseProofs = $purchaseProofRepository->createQueryBuilder('pp') + ->join('pp.item', 'i') + ->join('i.wishlist', 'w') + ->where('w.owner = :user') + ->setParameter('user', $user) + ->getQuery() + ->getResult(); + + return $this->render('user/purchase_proofs.html.twig', [ + 'purchaseProofs' => $purchaseProofs, + ]); + } + + +} \ No newline at end of file diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 1412642c4b6c9bac3d43f98ef84e790d7df2b8e3..3ebfe22ed9782e4b5c457b8848f4f647de6ce39b 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -39,7 +39,7 @@ class Item #[ORM\Column] private ?float $price = null; - #[ORM\OneToOne(mappedBy: 'item', cascade: ['persist', 'remove'])] + #[ORM\OneToOne(mappedBy: 'item', targetEntity: PurchaseProof::class, cascade: ['persist', 'remove'])] private ?PurchaseProof $purchaseProof = null; #[ORM\ManyToOne(inversedBy: 'items')] diff --git a/src/Entity/PurchaseProof.php b/src/Entity/PurchaseProof.php index cbaf858b9f0fc716eb976dea7fb9dce980cb3e3b..3667d91336b614fb18316721b0eb26c1dd02b732 100644 --- a/src/Entity/PurchaseProof.php +++ b/src/Entity/PurchaseProof.php @@ -19,12 +19,13 @@ class PurchaseProof #[ORM\Column(length: 255)] private ?string $imagePath = null; - #[ORM\OneToOne(inversedBy: "purchaseProof", cascade: ["persist", "remove"])] + #[ORM\OneToOne(inversedBy: 'purchaseProof', cascade: ['persist', 'remove'])] #[ORM\JoinColumn(nullable: false)] private ?Item $item = null; - #[ORM\Column] - private ?int $item_id = null; // Explicit item_id column + #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'purchaseProofs')] + #[ORM\JoinColumn(nullable: false)] + private ?User $buyer = null; public function getId(): ?int { diff --git a/src/Entity/User.php b/src/Entity/User.php index 406c8d520278523a95f205da16acde3176a2d10a..8a0a1ed285e932b5dbdf9e3a0f4eca855a710401 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -32,7 +32,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Id] #[ORM\GeneratedValue] - #[ORM\Column] + #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\Column(length: 255, unique: true)] @@ -59,8 +59,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Wishlist::class)] private Collection $wishlists; - #[ORM\OneToMany(mappedBy: 'invitedUser', targetEntity: Item::class)] - private Collection $invitations; + // #[ORM\OneToMany(mappedBy: 'invitedUser', targetEntity: Item::class)] + // private Collection $invitations; + + + #[ORM\OneToMany(mappedBy: 'buyer', targetEntity: PurchaseProof::class, cascade: ['persist', 'remove'])] + private Collection $purchaseProofs; public function __construct() { diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php index 89c463ad2a136d76a099b5bd4a20cb14d5d139e0..663fb6b845b3f88533933ee54b5357ba22c77ed8 100644 --- a/src/Form/RegistrationFormType.php +++ b/src/Form/RegistrationFormType.php @@ -40,8 +40,13 @@ class RegistrationFormType extends AbstractType ->add('password', PasswordType::class, [ 'label' => 'Mot de passe', 'constraints' => [ - new NotBlank(), - new Length(['min' => 6]), + new NotBlank([ + 'message' => 'Le mot de passe ne peut pas être vide.', + ]), + new Length([ + 'min' => 6, + 'minMessage' => 'Veuillez choisir un mot de passe contenant au moins {{ limit }} caractères.', + ]), ], ]) ->add('image', FileType::class, [ diff --git a/templates/base.html.twig b/templates/base.html.twig index 213ebafff224acd34ceb8ae6605b8a2da7215aae..6e00c3f737e022dd8c4a58cfe21428c55c0574d8 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -31,4 +31,10 @@ {% block body %}{% endblock %} </body> + + <footer> + <div style="text-align: center; margin-top: 20px;"> + <a href="{{ path('homepage') }}" class="btn btn-primary">Retour à la page d'accueil</a> + </div> +</footer> </html> diff --git a/templates/home/index.html.twig b/templates/home/index.html.twig index 3e984d3547fba0cb1420cfc147d8ca6246eafac0..dcadb7859532a4c8676e754d18175aae8db03f48 100644 --- a/templates/home/index.html.twig +++ b/templates/home/index.html.twig @@ -1,6 +1,33 @@ -{% extends 'base.html.twig' %} +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <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 %} + <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> + {% 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 title %}Homepage{% endblock %} {% block body %} <style> diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig index 1f9833dab4f5fb7ea0d896e4431a7ff418c77a1e..674cedbe54d48c561b8145672e4fb21e01fdc91d 100644 --- a/templates/registration/register.html.twig +++ b/templates/registration/register.html.twig @@ -10,7 +10,15 @@ {{ form_row(registrationForm.firstName) }} {{ form_row(registrationForm.lastName) }} {{ form_row(registrationForm.email) }} - {{ form_row(registrationForm.password) }} + + + {{ form_label(registrationForm.password) }} + {{ form_widget(registrationForm.password) }} + {% for error in registrationForm.password.vars.errors %} + <div class="text-danger">{{ error.message }}</div> + {% endfor %} + + {{ form_row(registrationForm.image) }} <button type="submit" class="btn btn-primary">S'inscrire</button> {{ form_end(registrationForm) }} diff --git a/templates/user/purchase_proofs.html.twig b/templates/user/purchase_proofs.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..f27b7e2f22133f28cfa5ea491b365339820f3b6e --- /dev/null +++ b/templates/user/purchase_proofs.html.twig @@ -0,0 +1,48 @@ +{% extends 'base.html.twig' %} + +{% block title %}Purchase Proofs{% endblock %} + +{% block body %} + <header> + <div class="user-icon"></div> + <h1><a href="#">Purchase Proof Management</a></h1> + <input type="text" placeholder="Search…" class="search-bar"> + </header> + + <main> + <div class="container"> + <section class="form-section"> + <h1>Purchase Proofs</h1> + + <table class="table"> + <thead> + <tr> + <th>Id</th> + <th>Congrats Text</th> + <th>Image Path</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {% for proof in purchaseProofs %} + <tr> + <td>{{ proof.id }}</td> + <td>{{ proof.congratsText }}</td> + <td> + <img src="{{ asset(proof.imagePath) }}" alt="Proof Image" style="max-width: 100px;"> + </td> + <td> + <a href="{{ path('app_purchase_proof', { 'id': proof.id }) }}">View</a> + </td> + </tr> + {% else %} + <tr> + <td colspan="4">No purchase proofs found.</td> + </tr> + {% endfor %} + </tbody> + </table> + </section> + </div> + </main> +{% endblock %} \ No newline at end of file