feat/cnx_api #11

Merged
maurine merged 15 commits from feat/cnx_api into main 2026-03-30 12:08:52 +02:00
16 changed files with 153 additions and 159 deletions
Showing only changes of commit e7e6d7c1af - Show all commits

0
[all
View File

View File

@@ -1,18 +0,0 @@
services:
###> doctrine/doctrine-bundle ###
database:
ports:
- "5432"
###< doctrine/doctrine-bundle ###
###> symfony/mailer ###
mailer:
image: axllent/mailpit
ports:
- "1025"
- "8025"
environment:
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
###< symfony/mailer ###

View File

@@ -25,29 +25,5 @@ services:
environment:
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
###> doctrine/doctrine-bundle ###
database:
image: postgres:${POSTGRES_VERSION:-16}-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-app}
# You should definitely change the password in production
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
POSTGRES_USER: ${POSTGRES_USER:-app}
healthcheck:
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
timeout: 5s
retries: 5
start_period: 60s
volumes:
- database_data:/var/lib/postgresql/data:rw
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
# - ./docker/db/data:/var/lib/postgresql/data:rw
###< doctrine/doctrine-bundle ###
volumes:
database_data:
###> doctrine/doctrine-bundle ###
database_data:
###< doctrine/doctrine-bundle ###

View File

@@ -1,34 +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 Version20260313104837 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 INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, role VARCHAR(255) NOT NULL, mail VARCHAR(255) NOT NULL, mail_quota VARCHAR(255) NOT NULL, mail_de_secours VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
}
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

@@ -20,7 +20,7 @@ final class Version20260313151403 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, email_de_secours VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, lastname VARCHAR(255) NOT NULL, firstname VARCHAR(255) NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, lastname VARCHAR(255) NOT NULL, firstname 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 BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');

View File

@@ -20,12 +20,12 @@ final class Version20260316103235 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE "user" ALTER email_de_secours DROP NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE "user" ALTER email_de_secours SET NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL');
}
}

View File

@@ -24,7 +24,7 @@ final class Version20260316114715 extends AbstractMigration
$this->addSql('ALTER TABLE "user" ADD first_name VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE "user" DROP lastname');
$this->addSql('ALTER TABLE "user" DROP firstname');
$this->addSql('ALTER TABLE "user" ALTER email_de_secours SET NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER identifiant_kaz SET NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER quota SET NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access SET NOT NULL');
@@ -39,7 +39,7 @@ final class Version20260316114715 extends AbstractMigration
$this->addSql('ALTER TABLE "user" ADD firstname VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE "user" DROP last_name');
$this->addSql('ALTER TABLE "user" DROP first_name');
$this->addSql('ALTER TABLE "user" ALTER email_de_secours DROP NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER identifiant_kaz DROP NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER quota DROP NOT NULL');
$this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access DROP NOT NULL');

View File

@@ -27,7 +27,7 @@ class UserController extends AbstractController
* Permet de vérifier si un utilisateur existe dans le ldap.
*
* @param string $email L'adresse e-mail de l'utilisateur.
* @param KazApiService $apiClient Le service utilisé pour récupérer les données utilisateur.
* @param KazApiService $apiKazService Le service utilisé pour récupérer les données utilisateur.
*
* @return Response La page index utilisateur rendue.
* @throws ClientExceptionInterface
@@ -37,29 +37,32 @@ class UserController extends AbstractController
* @throws TransportExceptionInterface
*/
# #[Route('/user/{email}', name: 'app_user', methods: ['GET'])]
# public function index(string $email, KazApiService $apiClient): Response
# {
# $exist = $apiClient->getUserData($email);
#
# return $this->render('user/index.html.twig', [
# 'exist' => $exist,
# ]);
# }
#[Route('/user/{email}', name: 'app_user_by_mail', methods: ['GET'])]
public function index(string $email, KazApiService $apiKazService): Response
{
$user = $apiKazService->getUserData($email);
return $this->render('user/profil_infos.html.twig', [
'user' => $user,
]);
}
/* TODO : Param l'API avec un Serializer pour la lecture du fichier JSON ? */
/* TODO : Param l'API avec un Serializer pour la lecture du fichier JSON ? */
#[Route('/mon-profil', name: 'app_user', methods: ['GET', 'POST'])]
#[IsGranted('ROLE_USER')]
public function showProfile(
Request $request,
EntityManagerInterface $entityManager,
FileUploader $fileUploader
FileUploader $fileUploader,
KazApiService $apiKazService
): Response {
# Récupération de l'utilisateur actuellement connecté
$user = $this->getUser();
/* Utilisation des fixtures pour vérifier la mise en page.
TODO: modifier pour que ça communique avec l'API */
$kazUser = $apiKazService->getUserData($user->getEmail());
$user = $user->updateFromKazUser($kazUser);
//TODO: modifier pour que ça communique avec l'API */
# Création du formulaire lié à l'utilisateur connecté
$form = $this->createForm(UserProfileType::class, $user);
@@ -82,6 +85,22 @@ class UserController extends AbstractController
$user->setPhoto($newFilename);
}
$alternateEmail = $form->get('alternateEmail')->getData();
$regexEmail = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
if(isset($alternateEmail) && preg_match($regexEmail, $alternateEmail)) {
$user->setAlternateEmail($form->get('alternateEmail')->getData());
} else {
$alternateEmail->addError(new FormError('L\'adresse e-mail n\'est pas valide.'));
}
$telephone = $form->get('telephone')->getData();
$regexTelephone = '/^[0-9\+\s\.\-\(\)]+$/';
if(isset($telephone) && preg_match($regexTelephone, $telephone)) {
$user->setTelephone($telephone);
} else {
$telephone->addError(new FormError('Le numéro de téléphone n\'est pas valide.'));
}
# Sauvegarde en base de données
$entityManager->flush();
@@ -105,9 +124,6 @@ class UserController extends AbstractController
EntityManagerInterface $entityManager
): Response
{
# Récupération de l'utilisateur actuellement connecté
$user = $this->getUser();
# Création du formulaire
$form = $this->createForm(ChangePasswordType::class);

View File

@@ -42,37 +42,55 @@ class AppFixtures extends Fixture
$user->setNextcloudQuota($faker->numberBetween(1, 20) . 'G');
$user->setQuota($faker->numberBetween(1, 10) . 'G');
$user->setEmailQuota('1G');
$user->setEmailDeSecours($faker->unique()->safeEmail());
$user->setAlternateEmail($faker->unique()->safeEmail());
$user->setHasAgoraAccess($faker->boolean(70)); // 70% de chance d'avoir accès
$user->setHasMobilizon($faker->boolean(50));
$user->setHasNextcloudAccess($faker->boolean(90));
$user->setIdentifiantKaz($faker->uuid());
}
// Création d'un compte de test fixe
$admin = new User();
$admin->setEmail('admin@kaz.bzh');
$admin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']);
$admin->setPassword($this->hasher->hashPassword($admin, 'password'));
$admin->setFirstName('Admin');
$admin->setLastName('KAZ');
// Remplissage des champs obligatoires restants pour éviter les erreurs SQL
$admin->setEmailDeSecours('secours@kaz.bzh');
$admin->setIdentifiantKaz('ADMIN-KAZ-001');
$admin->setQuota('5G');
$admin->setEmailQuota('1G');
$admin->setNextcloudQuota('10G');
$admin->setHasNextcloudAccess(true);
$admin->setHasMobilizon(true);
$admin->setHasAgoraAccess(true);
$manager->persist($admin);
# Préparation de l'enregistrement de l'objet en base de données
$manager->persist($user);
# Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie
$manager->flush();
}
// Création d'un compte de test fixe
$admin = new User();
$admin->setEmail('admin@kaz.bzh');
$admin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']);
$admin->setPassword($this->hasher->hashPassword($admin, 'password'));
$admin->setFirstName('Admin');
$admin->setLastName('KAZ');
// Remplissage des champs obligatoires restants pour éviter les erreurs SQL
$admin->setAlternateEmail('secours@kaz.bzh');
$admin->setIdentifiantKaz('ADMIN-KAZ-001');
$admin->setQuota('5G');
$admin->setEmailQuota('1G');
$admin->setNextcloudQuota('10G');
$admin->setHasNextcloudAccess(true);
$admin->setHasMobilizon(true);
$admin->setHasAgoraAccess(true);
$manager->persist($admin);
// Création d'un compte de test fixe
$melvin = new User();
$melvin->setEmail('melvin.leveque@kazkouil.fr');
$melvin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']);
$melvin->setPassword($this->hasher->hashPassword($melvin, 'password'));
$melvin->setFirstName('');
$melvin->setLastName('');
$melvin->setAlternateEmail('');
$melvin->setIdentifiantKaz('MELVIN-KAZ-001');
$melvin->setQuota('5G');
$melvin->setEmailQuota('1G');
$melvin->setNextcloudQuota('10G');
$melvin->setHasNextcloudAccess(true);
$melvin->setHasMobilizon(true);
$melvin->setHasAgoraAccess(true);
$manager->persist($melvin);
# Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie
$manager->flush();
}
}

View File

@@ -12,14 +12,16 @@ use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
public const string EMAIL_QUOTA_DEFAULT = '1G';
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\Column(type: 'uuid', unique: true)]
#[ORM\Column(type: 'uuid', unique: true, name: 'id')]
private ?Uuid $id;
#[ORM\Column(length: 180, unique: true)]
@@ -28,51 +30,56 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
/**
* @var list<string> The user roles
*/
#[ORM\Column]
#[ORM\Column(name: 'roles')]
private array $roles = [];
/**
* @var ?string The hashed password
*/
#[ORM\Column]
#[ORM\Column(name: 'password')]
private ?string $password = null;
#[ORM\Column(length: 255)]
private ?string $emailQuota = '1G';
#[ORM\Column(length: 255, name: 'email_quota')]
private ?string $emailQuota = null;
#[ORM\Column(length: 255)]
private ?string $emailDeSecours = null;
#[ORM\Column(length: 255, name: 'alternate_email')]
private ?string $alternateEmail = null;
#[ORM\Column(length: 255)]
#[ORM\Column(length: 255, name: 'identifiant_kaz')]
private ?string $identifiantKaz = null;
#[ORM\Column(length: 255)]
#[ORM\Column(length: 255, name: 'quota')]
private ?string $quota = null;
#[ORM\Column]
#[ORM\Column(name: 'has_nextcloud_access')]
private ?bool $hasNextcloudAccess = null;
#[ORM\Column(length: 255)]
#[ORM\Column(length: 255, name: 'nextcloud_quota')]
private ?string $nextcloudQuota = null;
#[ORM\Column]
#[ORM\Column(name: 'has_mobilizon')]
private ?bool $hasMobilizon = null;
#[ORM\Column]
#[ORM\Column(name: 'has_agora_access')]
private ?bool $hasAgoraAccess = null;
#[ORM\Column(length: 255)]
#[ORM\Column(length: 255, name: 'last_name')]
private ?string $lastName = null;
#[ORM\Column(length: 255)]
#[ORM\Column(length: 255, name: 'first_name')]
private ?string $firstName = null;
#[ORM\Column(length: 255, nullable: true)]
// TODO: Modifier "photo" par "image"
#[ORM\Column(length: 255, nullable: true, name: 'photo')]
private ?string $photo = null;
#[ORM\Column(length: 20, nullable: true)]
#[ORM\Column(length: 20, nullable: true, name: 'telephone')]
private ?string $telephone = null;
public function __construct() {
$this->emailQuota = self::EMAIL_QUOTA_DEFAULT;
}
public function getId(): ?Uuid
{
return $this->id;
@@ -175,14 +182,14 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
public function getEmailDeSecours(): ?string
public function getAlternateEmail(): ?string
{
return $this->emailDeSecours;
return $this->alternateEmail;
}
public function setEmailDeSecours(string $emailDeSecours): static
public function setAlternateEmail(string $alternateEmail): static
{
$this->emailDeSecours = $emailDeSecours;
$this->alternateEmail = $alternateEmail;
return $this;
}
@@ -306,4 +313,19 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
public function updateFromKazUser($kazUser) : User
{
$this->setEmail($kazUser['mail']);
// Création du firstname et lastname
$name = explode(' ', $kazUser['sn']);
$this->setFirstName($name[0]);
// Récupération des valeurs du tableau moins la première
$aLastname = array_slice($name, 1);
$this->setLastName(implode(' ', $aLastname));
//TODO: Ajouter les champs manquants de l'objet User dans l'api kaz.
return $this;
}
}

View File

@@ -31,7 +31,7 @@ class UserProfileType extends AbstractType
'label' => 'E-mail',
'disabled' => true,
])
->add('emailDeSecours', EmailType::class, ['label' => 'E-mail de secours'])
->add('alternateEmail', EmailType::class, ['label' => 'E-mail de secours'])
->add('telephone', TelType::class, [
'label'=>'Téléphone',
'required' => false,

View File

@@ -34,7 +34,7 @@ class FileUploader
public function delete(?string $fileName): void
{
if ($fileName) {
$filePath = $this->getTargetDirectory() . '/' . $fileName;
$filePath = $this->getTargetDirectory() . 'FileUploader.php/' . $fileName;
if (file_exists($filePath)) {
unlink($filePath);
}

View File

@@ -14,12 +14,18 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
class KazApiService
{
private ?string $token = null;
private HttpClientInterface $httpClient;
public function __construct(
private readonly HttpClientInterface $kazApiClient,
private readonly string $apiUser,
private readonly string $apiPassword
) {}
) {
$this->httpClient = $kazApiClient->withOptions([
'auth_basic' => [$apiUser, $apiPassword]
]);
}
/**
* Récupère le token JWT via l'authentification Basic
@@ -37,16 +43,14 @@ class KazApiService
return $this->token;
}
$response = $this->kazApiClient->request('POST', '/get_token', [
'auth_basic' => [$this->apiUser, $this->apiPassword]
]);
$response = $this->httpClient->request('GET', '/get_token');
if ($response->getStatusCode() !== 200) {
throw new Exception('Impossible de récupérer le token JWT');
throw new Exception('Impossible de récupérer le token JWT'.$response->getStatusCode());
}
$data = $response->toArray();
$this->token = $data['token']; // Ajustez la clé selon le format de votre API
$this->token = $data['access_token']; // Ajustez la clé selon le format de votre API
return $this->token;
}
@@ -68,7 +72,6 @@ class KazApiService
public function getUserData(string $email): array
{
$options['headers']['Authorization'] = 'Bearer ' . $this->getToken();
$response = $this->kazApiClient->request('GET', "/ldap/user/$email", $options);
if ($response->getStatusCode() !== 200) {
@@ -77,4 +80,4 @@ class KazApiService
return $response->toArray();
}
}
}

View File

@@ -110,15 +110,15 @@
{# Champ E-mail de secours #}
<div class="space-y-1">
{{ form_label(form.emailDeSecours, 'E-mail de secours :', {
{{ form_label(form.alternateEmail, 'E-mail de secours :', {
'label_attr': {'class': 'block text-sm font-semibold text-text'}
}) }}
{{ form_widget(form.emailDeSecours, {
{{ form_widget(form.alternateEmail, {
'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}
}) }}
{# Implémentation d'un message d'errer en cas de problème #}
<div class="text-red-500 text-xs mt-1 italic font-sora">
{{ form_errors(form.emailDeSecours) }}
{{ form_errors(form.alternateEmail) }}
</div>
</div>

View File

@@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block title %}Accueil | {{ parent() }}{% endblock %}
{% block body %}
<div class="min-h-screen bg-bg-primaire py-8 w-full font-sora">
<div>
<span>Identifiant : {{ user.identifiantKaz }}</span>
</div>
</div>
{% endblock %}

0
true],
View File