Ajout gestion des utilisateurs

This commit is contained in:
Melaine Gérard 2024-12-29 16:00:42 +01:00
parent 89e2ccce02
commit 2fb1414228
13 changed files with 338 additions and 10 deletions

View File

@ -47,6 +47,7 @@ class CreateUserCommand extends Command
$user->setEmail($email);
$user->setPassword($this->passwordHasher->hashPassword($user, $password));
$user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']);
$user->initId();
$this->entityManager->persist($user);
$this->entityManager->flush();

View File

@ -0,0 +1,91 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\UserAdminType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
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\Security\Http\Attribute\IsGranted;
#[IsGranted('ROLE_ADMIN')]
#[Route('/admin', name: 'app_admin_')]
class AdminController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly UserPasswordHasherInterface $passwordEncoder,
private readonly UserRepository $userRepository
) {
}
#[Route('/', name: 'index')]
public function index(): Response
{
return $this->render('admin/index.html.twig');
}
#[Route('/users', name: 'user_index')]
public function indexUsers(): Response
{
$users = $this->userRepository->findAll();
return $this->render('admin/user_index.html.twig', [
'users' => $users,
]);
}
#[Route('/users/create', name: 'user_create')]
#[Route('/users/edit/{user}', name: 'user_edit')]
public function editUsers(#[MapEntity(id: 'user')] ?User $user, Request $request): Response
{
$isNew = false;
if (!$user) {
$user = new User();
$isNew = true;
}
$form = $this->createForm(UserAdminType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$role = $form->get('role')->getData();
$user->setRoles([$role]);
$user->initId();
if ($form->has('plainPassword')) {
$plainPassword = $form->get('plainPassword')->getData();
$user->setPassword($this->passwordEncoder->hashPassword($user, $plainPassword));
}
$this->entityManager->persist($user);
$this->entityManager->flush();
$this->addFlash('success', 'L\'utilisateur a bien été enregistré !');
return $this->redirectToRoute('app_admin_user_index');
}
return $this->render('admin/user_edit.html.twig', [
'form' => $form->createView(),
'user' => $user,
'isNew' => $isNew,
]);
}
#[Route('/users/delete/{user}', name: 'user_delete')]
public function deleteUser(#[MapEntity(id: 'user')] User $user): Response
{
$this->entityManager->remove($user);
$this->entityManager->flush();
$this->addFlash('success', 'L\'utilisateur a bien été supprimé !');
return $this->redirectToRoute('app_admin_user_index');
}
}

View File

@ -16,7 +16,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/files', 'app_files_')]
#[IsGranted('ROLE_USER')]
class DashboardController extends AbstractController
class FilesController extends AbstractController
{
/**
* @throws FilesystemException
@ -62,7 +62,7 @@ class DashboardController extends AbstractController
}
});
return $this->render('dashboard/index.html.twig', [
return $this->render('files/index.html.twig', [
'files' => $realFiles,
'path' => $path,
]);

View File

@ -17,7 +17,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', length: 180)]
private ?Uuid $id;
private ?Uuid $id = null;
#[ORM\Column(length: 255)]
private ?string $email = null;
@ -34,8 +34,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column]
private ?string $password = null;
public function __construct()
public function initId(): void
{
if ($this->id !== null) {
return;
}
$this->id = Uuid::v4();
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserAdminType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, [
'label' => 'Adresse email',
])
->add('role', ChoiceType::class, [
'label' => 'Rôle',
'choices' => [
'Utilisateur' => 'ROLE_USER',
'Administrateur' => 'ROLE_ADMIN',
],
'mapped' => false,
])
;
// Si l'utilisateur est nouveau, on ajoute le champ de mot de passe
if (!$options['data']->getId()) {
$builder->add('plainPassword', PasswordType::class, [
'label' => 'Mot de passe',
'required' => true,
'mapped' => false,
]);
} else {
// On set le rôle actuel de l'utilisateur
$builder->get('role')->setData($options['data']->getRoles()[0]);
}
$builder->add('submit', SubmitType::class, [
'label' => 'Enregistrer',
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View File

@ -0,0 +1,15 @@
{% extends 'base-admin.html.twig' %}
{% block title %}Le cloud de Camélia-Studio{% endblock %}
{% block body %}
<div class="min-h-96 flex items-center flex-col justify-center">
<h1 class="text-4xl font-bold mb-2 text-gray-900 dark:text-white">Bienvenue sur l'administration</h1>
<p class="text-xl text-gray-600 dark:text-gray-300 mb-8">Gérez facilement les accès des membres de Camélia-Studio à l'espace de stockage partagé Kumora. Ajoutez, modifiez ou retirez les utilisateurs en quelques clics.</p>
<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">
Gérer les utilisateurs
</a>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends 'base-admin.html.twig' %}
{% 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">
{% if not isNew %}
Edition de l'utilisateur {{ user.email }}
{% else %}
Création d'un nouvel utilisateur
{% endif %}
</h3>
<div class="mt-4">
{{ include('partials/alerts.html.twig') }}
</div>
{{ form(form) }}
</div>
</div>
{% endblock %}
{% block title %}
{% if not isNew %}
Edition de l'utilisateur {{ user.email }}
{% else %}
Création d'un nouvel utilisateur
{% endif %}
{% endblock %}

View File

@ -0,0 +1,63 @@
{% extends 'base-admin.html.twig' %}
{% block body %}
<div class="container mx-auto px-16 mt-4">
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Liste des utilisateurs</h5>
<div class="flex justify-end">
<a href="{{ path('app_admin_user_create') }}" 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">Créer un utilisateur</a>
</div>
<div class="mt-4">
{{ include('partials/alerts.html.twig') }}
</div>
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-4">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Id
</th>
<th scope="col" class="px-6 py-3">
Email
</th>
<th scope="col" class="px-6 py-3">
Rôle
</th>
<th scope="col" class="px-6 py-3">
Action
</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{{ user.id }}
</th>
<td class="px-6 py-4">
{{ user.email }}
</td>
<td class="px-6 py-4">
{{ user.roles[0] == 'ROLE_ADMIN' ? 'Administrateur' : 'Utilisateur' }}
</td>
<td class="px-6 py-4 flex gap-2 light:text-black">
<a href="{{ path('app_admin_user_edit', {
user: user.id
}) }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
<a href="{{ path('app_admin_user_delete', {
user: user.id
}) }}" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block title %}
Liste des utilisateurs
{% endblock %}

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="fr" class="bg-white dark:bg-gray-800 dark:text-white">
<head>
<meta charset="UTF-8">
<title>Kumora - {% block title %}Accueil{% endblock %}</title>
<link rel="icon" href="{{ asset('images/favicon.ico') }}">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
</head>
<body data-turbo="false">
{% include "partials/navbar-admin.html.twig" %}
{% block body %}{% endblock %}
</body>
</html>

View File

@ -5,11 +5,13 @@
{% block body %}
<div class="container mx-auto px-16">
<h1 class="text-center text-2xl font-bold mt-4">Liste des fichiers</h1>
<div class="mt-4">
{{ include('partials/alerts.html.twig') }}
</div>
<div class="mt-4">
{% include 'partials/breadbrumb.html.twig' %}
</div>
<div class="relative overflow-x-auto mt-4">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
@ -49,13 +51,11 @@
</td>
<td class="px-6 py-4 flex gap-2 light:text-black">
{% if file.type == 'file' %}
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:eye" /></a>
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:download" /></a>
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:link" /></a>
<a href="{{ file.url }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:download" /></a>
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
<a href="#" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
{% else %}
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:link" /></a>
<a href="{{ file.url }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:link" /></a>
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
<a href="#" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
{% endif %}

View File

@ -0,0 +1,21 @@
{% for label, messages in app.flashes %}
{% for message in messages %}
{% if label == 'success' %}
<div class="border border-green-300 dark:border-green-800 p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400" role="alert">
{{ message }}
</div>
{% elseif label == 'info' %}
<div class="border border-blue-300 dark:border-blue-800 p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" role="alert">
{{ message }}
</div>
{% elseif label == 'error' %}
<div class="border border-red-300 dark:border-red-800 p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
{{ message }}
</div>
{% elseif label == 'warning' %}
<div class="border border-yellow-300 dark:border-yellow-800 p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300" role="alert">
{{ message }}
</div>
{% endif %}
{% endfor %}
{% endfor %}

View File

@ -0,0 +1,28 @@
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a href="{{ path('app_home') }}" class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="{{ asset('images/logo.png') }}" class="h-8" alt="Logo Camélia-Studio" />
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Kumora</span>
</a>
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"></path>
</svg>
</button>
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
<li>
<a href="{{ path('app_admin_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>
<li>
<a href="{{ path('app_admin_user_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">Gestion des utilisateurs</a>
</li>
<li>
<a href="{{ path('app_home') }}" 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">Retour à l'accueil</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@ -25,7 +25,7 @@
{% 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>
<a href="{{ path('app_admin_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>