feat: création boîte mail Esy-Mail lors de la création client
EsyMailService : - createMailbox(email, password, quotaMb) : INSERT dans la table mailbox de la base esymail avec hash bcrypt (BLF-CRYPT compatible Dovecot) - mailboxExists(email) : vérifie si l'adresse existe déjà - isAvailable() : vérifie si ESYMAIL_DATABASE_URL est configuré - Connexion DBAL directe vers la base esymail (séparée de l'EntityManager) ClientsController : - Ajout paramètre EsyMailService dans create() - Ajout méthode createMailboxIfRequested() : vérifie checkbox, valide email/password, vérifie existence, crée la boîte avec quota choisi - Flash success/error selon le résultat Template admin/clients/create.html.twig : - Section "Messagerie Esy-Mail" avec checkbox toggle - Champs : adresse email, mot de passe (min 8 chars), quota (1/2/5/10 Go) - Masqué par défaut, affiché au clic sur la checkbox Configuration : - .env : ajout ESYMAIL_DATABASE_URL (vide par défaut) - .env.local : connexion vers database:5432/esymail Tests mis à jour avec EsyMailService stubé dans tous les appels create() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
.env
4
.env
@@ -116,3 +116,7 @@ DOCUSEAL_WEBHOOKS_SECRET=
|
||||
###> discord ###
|
||||
DISCORD_WEBHOOK=
|
||||
###< discord ###
|
||||
|
||||
###> esymail ###
|
||||
ESYMAIL_DATABASE_URL=
|
||||
###< esymail ###
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Controller\Admin;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\User;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Service\EsyMailService;
|
||||
use App\Service\MeilisearchService;
|
||||
use App\Service\UserManagementService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -39,6 +40,7 @@ class ClientsController extends AbstractController
|
||||
MeilisearchService $meilisearch,
|
||||
UserManagementService $userService,
|
||||
LoggerInterface $logger,
|
||||
EsyMailService $esyMailService,
|
||||
#[Autowire(env: 'STRIPE_SK')] string $stripeSecretKey,
|
||||
): Response {
|
||||
if ('POST' !== $request->getMethod()) {
|
||||
@@ -66,6 +68,7 @@ class ClientsController extends AbstractController
|
||||
$em->flush();
|
||||
|
||||
$this->indexInMeilisearch($meilisearch, $customer, $logger);
|
||||
$this->createMailboxIfRequested($request, $esyMailService);
|
||||
|
||||
$this->addFlash('success', 'Client '.$customer->getFullName().' cree.');
|
||||
|
||||
@@ -134,6 +137,35 @@ class ClientsController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
private function createMailboxIfRequested(Request $request, EsyMailService $esyMailService): void
|
||||
{
|
||||
if (!$request->request->getBoolean('createMailbox')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mailboxEmail = trim($request->request->getString('mailboxEmail'));
|
||||
$mailboxPassword = $request->request->getString('mailboxPassword');
|
||||
$quota = $request->request->getInt('mailboxQuota', 5120);
|
||||
|
||||
if ('' === $mailboxEmail || '' === $mailboxPassword) {
|
||||
$this->addFlash('error', 'Esy-Mail : email et mot de passe requis.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($esyMailService->mailboxExists($mailboxEmail)) {
|
||||
$this->addFlash('error', 'Esy-Mail : la boite '.$mailboxEmail.' existe deja.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($esyMailService->createMailbox($mailboxEmail, $mailboxPassword, $quota)) {
|
||||
$this->addFlash('success', 'Boite mail '.$mailboxEmail.' creee.');
|
||||
} else {
|
||||
$this->addFlash('error', 'Esy-Mail : erreur lors de la creation de la boite mail.');
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/search', name: 'search', methods: ['GET'])]
|
||||
public function search(Request $request, MeilisearchService $meilisearch): JsonResponse
|
||||
{
|
||||
|
||||
94
src/Service/EsyMailService.php
Normal file
94
src/Service/EsyMailService.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||
|
||||
class EsyMailService
|
||||
{
|
||||
private ?Connection $conn = null;
|
||||
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
private LoggerInterface $logger,
|
||||
#[Autowire(env: 'ESYMAIL_DATABASE_URL')] private string $databaseUrl = '',
|
||||
) {
|
||||
}
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return '' !== $this->databaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une boîte mail dans la base esymail.
|
||||
*/
|
||||
public function createMailbox(string $email, string $password, int $quotaMb = 5120): bool
|
||||
{
|
||||
if (!$this->isAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parts = explode('@', $email);
|
||||
if (2 !== \count($parts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$domain = $parts[1];
|
||||
$hashedPassword = password_hash($password, \PASSWORD_BCRYPT);
|
||||
|
||||
try {
|
||||
$conn = $this->getConnection();
|
||||
$conn->insert('mailbox', [
|
||||
'email' => $email,
|
||||
'password' => $hashedPassword,
|
||||
'domain' => $domain,
|
||||
'quota_mb' => $quotaMb,
|
||||
'is_active' => true,
|
||||
'created_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:sP'),
|
||||
], [
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
$this->logger->info('EsyMail: boite mail creee pour '.$email);
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('EsyMail: erreur creation boite '.$email.': '.$e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une boîte mail existe.
|
||||
*/
|
||||
public function mailboxExists(string $email): bool
|
||||
{
|
||||
if (!$this->isAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->getConnection()->fetchOne('SELECT COUNT(*) FROM mailbox WHERE email = ?', [$email]);
|
||||
|
||||
return (int) $result > 0;
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getConnection(): Connection
|
||||
{
|
||||
if (null === $this->conn) {
|
||||
$this->conn = DriverManager::getConnection(['url' => $this->databaseUrl]);
|
||||
}
|
||||
|
||||
return $this->conn;
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,46 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="glass p-6">
|
||||
<h2 class="text-sm font-bold uppercase tracking-wider mb-4 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#fabf04]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Messagerie Esy-Mail
|
||||
<span class="text-xs font-normal text-gray-400 normal-case tracking-normal">(optionnel)</span>
|
||||
</h2>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<label for="createMailbox" class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" id="createMailbox" name="createMailbox" value="1" class="accent-[#fabf04]"
|
||||
onchange="document.getElementById('mailbox-fields').classList.toggle('hidden', !this.checked)">
|
||||
<span class="text-sm font-bold">Creer une boite mail pour ce client</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="mailbox-fields" class="hidden">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="mailboxEmail" class="block text-xs font-bold uppercase tracking-wider mb-2">Adresse email Esy-Mail *</label>
|
||||
<input type="email" id="mailboxEmail" name="mailboxEmail" placeholder="client@siteconseil.fr"
|
||||
class="w-full px-4 py-3 input-glass text-sm font-medium">
|
||||
</div>
|
||||
<div>
|
||||
<label for="mailboxPassword" class="block text-xs font-bold uppercase tracking-wider mb-2">Mot de passe messagerie *</label>
|
||||
<input type="password" id="mailboxPassword" name="mailboxPassword" placeholder="Min. 8 caracteres"
|
||||
class="w-full px-4 py-3 input-glass text-sm font-medium" minlength="8">
|
||||
</div>
|
||||
<div>
|
||||
<label for="mailboxQuota" class="block text-xs font-bold uppercase tracking-wider mb-2">Quota</label>
|
||||
<select id="mailboxQuota" name="mailboxQuota" class="w-full px-4 py-3 glass text-sm font-bold">
|
||||
<option value="1024">1 Go</option>
|
||||
<option value="2048">2 Go</option>
|
||||
<option value="5120" selected>5 Go</option>
|
||||
<option value="10240">10 Go</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-4">Le client sera automatiquement cree chez Stripe. Un mot de passe temporaire sera genere pour son acces a l'Espace Client.</p>
|
||||
<button type="submit"
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Controller\Admin\ClientsController;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\User;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Service\EsyMailService;
|
||||
use App\Service\MeilisearchService;
|
||||
use App\Service\UserManagementService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -73,7 +74,7 @@ class ClientsControllerTest extends TestCase
|
||||
$userService = $this->createStub(UserManagementService::class);
|
||||
$logger = $this->createStub(LoggerInterface::class);
|
||||
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, 'sk_test_***');
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, $this->createStub(EsyMailService::class), 'sk_test_***');
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
}
|
||||
@@ -93,7 +94,7 @@ class ClientsControllerTest extends TestCase
|
||||
$userService->method('createBaseUser')->willThrowException(new \InvalidArgumentException('Champs requis'));
|
||||
$logger = $this->createStub(LoggerInterface::class);
|
||||
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, 'sk_test_***');
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, $this->createStub(EsyMailService::class), 'sk_test_***');
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
}
|
||||
@@ -113,7 +114,7 @@ class ClientsControllerTest extends TestCase
|
||||
$userService->method('createBaseUser')->willThrowException(new \RuntimeException('DB error'));
|
||||
$logger = $this->createStub(LoggerInterface::class);
|
||||
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, 'sk_test_***');
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, $this->createStub(EsyMailService::class), 'sk_test_***');
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
}
|
||||
@@ -249,6 +250,7 @@ class ClientsControllerTest extends TestCase
|
||||
$this->createStub(MeilisearchService::class),
|
||||
$userService,
|
||||
$this->createStub(LoggerInterface::class),
|
||||
$this->createStub(EsyMailService::class),
|
||||
'',
|
||||
);
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
@@ -286,6 +288,7 @@ class ClientsControllerTest extends TestCase
|
||||
$this->createStub(MeilisearchService::class),
|
||||
$userService,
|
||||
$this->createStub(LoggerInterface::class),
|
||||
$this->createStub(EsyMailService::class),
|
||||
'sk_test_***',
|
||||
);
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
@@ -326,6 +329,7 @@ class ClientsControllerTest extends TestCase
|
||||
$meilisearch,
|
||||
$userService,
|
||||
$this->createStub(LoggerInterface::class),
|
||||
$this->createStub(EsyMailService::class),
|
||||
'',
|
||||
);
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
|
||||
Reference in New Issue
Block a user