changes to mail

This commit is contained in:
Flo 2024-09-14 13:50:28 +00:00
parent 6d97fc24c4
commit ed03bb79d6
14 changed files with 279 additions and 17 deletions

View File

@ -20,7 +20,8 @@ INIT_USER_PASSWORD=password
INIT_USER_MAIL=admin@test.com INIT_USER_MAIL=admin@test.com
# Mail # Mail
MAIL_DEFAULT_SENDER=info@bee.local MAIL_DEFAULT_SENDER=beekeeper@stack-up.de
MAIL_DEFAULT_SENDER_NAME=Beekeeper
SMTP_USERNAME=smtp@stack-up.de SMTP_USERNAME=smtp@stack-up.de
SMTP_PASSWORD= SMTP_PASSWORD=
SMTP_HOST= #srv-mail-01.lab.jonasf.de SMTP_HOST= #srv-mail-01.lab.jonasf.de

View File

@ -3,7 +3,8 @@
return [ return [
'mail' => [ 'mail' => [
'default' => [ 'default' => [
'sender' => $_ENV['MAIL_DEFAULT_SENDER'] 'sender' => $_ENV['MAIL_DEFAULT_SENDER'],
'senderName' => $_ENV['MAIL_DEFAULT_SENDER_NAME']
], ],
'smtp-server' => [ 'smtp-server' => [
'host' => $_ENV['SMTP_HOST'], 'host' => $_ENV['SMTP_HOST'],

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Bee\Migrations\Bee;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240911191301 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'mail'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE mail (
id binary(16) NOT NULL,
template varchar(255) NOT NULL,
data json NOT NULL,
recipient varchar(255) NOT NULL,
sender varchar(255) NULL,
sender_name varchar(255) NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE mail;");
}
}

View File

@ -2,10 +2,12 @@
use Bee\API\Console\Command\InitializeDataCommand; use Bee\API\Console\Command\InitializeDataCommand;
use Bee\API\Console\Command\RbacUpdateCommand; use Bee\API\Console\Command\RbacUpdateCommand;
use Bee\API\Console\Command\SendMailsCommand;
return [ return [
'commands' => [ 'commands' => [
InitializeDataCommand::class, InitializeDataCommand::class,
RbacUpdateCommand::class, RbacUpdateCommand::class,
SendMailsCommand::class,
] ]
]; ];

View File

@ -2,11 +2,13 @@
use Bee\API\Console\Command\InitializeDataCommand; use Bee\API\Console\Command\InitializeDataCommand;
use Bee\API\Console\Command\RbacUpdateCommand; use Bee\API\Console\Command\RbacUpdateCommand;
use Bee\API\Console\Command\SendMailsCommand;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory; use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [ return [
'factories' => [ 'factories' => [
InitializeDataCommand::class => AutoWiringFactory::class, InitializeDataCommand::class => AutoWiringFactory::class,
RbacUpdateCommand::class => AutoWiringFactory::class, RbacUpdateCommand::class => AutoWiringFactory::class,
SendMailsCommand::class => AutoWiringFactory::class,
], ],
]; ];

View File

@ -75,6 +75,7 @@ class InitializeDataCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
$io->success("Done");
return Command::SUCCESS; return Command::SUCCESS;
} }
} }

View File

@ -93,6 +93,7 @@ class RbacUpdateCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
$io->success("Done");
return Command::SUCCESS; return Command::SUCCESS;
} }
} }

View File

@ -0,0 +1,43 @@
<?php
namespace Bee\API\Console\Command;
use Bee\Infrastructure\Logging\Logger\Logger;
use Bee\Infrastructure\Mail\Service\MailService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'mails:send', description: 'Sends all mails left in the mail queue')]
class SendMailsCommand extends Command
{
public function __construct(
private readonly MailService $service,
private readonly Logger $logger,
) {
parent::__construct($this->getName());
}
protected function execute(
InputInterface $input,
OutputInterface $output
): int {
$io = new SymfonyStyle($input, $output);
try {
$this->service->send();
} catch (\Throwable $e) {
$io->error($e->getMessage());
$io->error($e->getTraceAsString());
$this->logger->error($e->getMessage(), ['exception' => $e]);
return Command::FAILURE;
}
$io->success("Done");
return Command::SUCCESS;
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Bee\API\External\Health\Handler; namespace Bee\API\External\Health\Handler;
use Bee\Infrastructure\Mail\Service\MailService;
use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
@ -11,7 +12,8 @@ use Psr\Http\Server\RequestHandlerInterface;
class HealthHandler implements RequestHandlerInterface class HealthHandler implements RequestHandlerInterface
{ {
public function __construct() { public function __construct(
) {
} }
public function handle(ServerRequestInterface $request): ResponseInterface public function handle(ServerRequestInterface $request): ResponseInterface
@ -19,5 +21,3 @@ class HealthHandler implements RequestHandlerInterface
return new JsonResponse("I'm fine. :)"); return new JsonResponse("I'm fine. :)");
} }
} }
?>

View File

@ -0,0 +1,112 @@
<?php
namespace Bee\Data\Business\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Bee\Infrastructure\UuidGenerator\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass="Bee\Data\Business\Repository\MailRepository")
* @ORM\Table(name="mail")
*/
class Mail {
/**
* @ORM\Id
* @ORM\Column(name="id", type="uuid_binary_ordered_time")
*/
private UuidInterface $id;
/** @ORM\Column(name="template", type="string") */
private string $template;
/** @ORM\Column(name="data", type="json") */
private array $data;
/** @ORM\Column(name="recipient", type="string") */
private string $recipient;
/** @ORM\Column(name="sender", type="string", nullable="true") */
private ?string $sender;
/** @ORM\Column(name="sender_name", type="string", nullable="true") */
private ?string $senderName;
/** @ORM\Column(name="created_at", type="datetime") */
private DateTime $createdAt;
public function __construct() {
$this->id = UuidGenerator::generate();
$now = new DateTime();
$this->setCreatedAt($now);
}
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*/
public function updateTimestamps(): void {
$now = new DateTime();
//$this->setUpdatedAt($now);
}
public function getId(): UuidInterface {
return $this->id;
}
public function getTemplate(): string
{
return $this->template;
}
public function setTemplate(string $template): void
{
$this->template = $template;
}
public function getData(): array
{
return $this->data;
}
public function setData(array $data): void
{
$this->data = $data;
}
public function getRecipient(): string
{
return $this->recipient;
}
public function setRecipient(string $recipient): void
{
$this->recipient = $recipient;
}
public function getSender(): ?string
{
return $this->sender;
}
public function setSender(?string $sender): void
{
$this->sender = $sender;
}
public function getSenderName(): ?string
{
return $this->senderName;
}
public function setSenderName(?string $senderName): void
{
$this->senderName = $senderName;
}
public function getCreatedAt(): DateTime {
return $this->createdAt;
}
public function setCreatedAt(DateTime $createdAt): void {
$this->createdAt = $createdAt;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Bee\Data\Business\Repository;
use Bee\Data\Business\Entity\User;
use Doctrine\ORM\EntityRepository;
class MailRepository extends EntityRepository {
}

View File

@ -29,7 +29,7 @@ class SendMailStep implements TaskInterface
$command = $payload->getCommand(); $command = $payload->getCommand();
$registration = $payload->getRegistration(); $registration = $payload->getRegistration();
$this->mailService->send( $this->mailService->enqueue(
template: 'registration', template: 'registration',
templateData: [ templateData: [
'username' => $command->getUsername(), 'username' => $command->getUsername(),
@ -40,9 +40,7 @@ class SendMailStep implements TaskInterface
$registration->getId()->toString() $registration->getId()->toString()
) )
], ],
recipient: $command->getMail(), recipient: $command->getMail()
sender: 'beekeeper@stack-up.de',
senderName: "Beekeeper"
); );
$pipeline->next()($payload, $pipeline); $pipeline->next()($payload, $pipeline);

View File

@ -50,7 +50,7 @@ class ForgotPasswordCommandHandler
$this->entityManager->persist($passwordToken); $this->entityManager->persist($passwordToken);
$this->entityManager->flush(); $this->entityManager->flush();
$this->mailService->send( $this->mailService->enqueue(
'reset-password', 'reset-password',
templateData: [ templateData: [
'username' => $user->getUsername(), 'username' => $user->getUsername(),
@ -61,8 +61,6 @@ class ForgotPasswordCommandHandler
) )
], ],
recipient: $mail, recipient: $mail,
sender: 'beekeeper@stack-up.de',
senderName: 'Beekeeper'
); );
return new ForgotPasswordCommandResult(); return new ForgotPasswordCommandResult();

View File

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace Bee\Infrastructure\Mail\Service; namespace Bee\Infrastructure\Mail\Service;
use Bee\Data\Business\Entity\Mail;
use Bee\Data\Business\Manager\EntityManager;
use Bee\Data\Business\Repository\MailRepository;
use Exception; use Exception;
use Latte\Engine; use Latte\Engine;
use Nette\Mail\Mailer; use Nette\Mail\Mailer;
@ -18,18 +21,70 @@ class MailService
{ {
private const TEMPLATE_PATH = APP_ROOT . '/data/mails/'; private const TEMPLATE_PATH = APP_ROOT . '/data/mails/';
private readonly Engine $engine; private readonly Engine $engine;
private readonly MailRepository $mailRepository;
public function __construct( public function __construct(
private readonly EntityManager $entityManager,
private readonly ConfigService $configService, private readonly ConfigService $configService,
private readonly Logger $logger, private readonly Logger $logger,
) { ) {
$this->engine = new Engine(); $this->engine = new Engine();
$this->mailRepository = $this->entityManager->getRepository(Mail::class);
}
public function enqueue(
string $template,
array $templateData,
string $recipient,
?string $sender = null,
?string $senderName = null
): void {
$mail = new Mail();
$mail->setTemplate($template);
$mail->setData($templateData);
$mail->setRecipient($recipient);
$mail->setSender($sender);
$mail->setSenderName($senderName);
$this->entityManager->persist($mail);
$this->entityManager->flush();
}
public function send(?int $count = null): void {
$qb = $this->mailRepository->createQueryBuilder('m');
$qb->orderBy('m.createdAt', 'asc');
if ($count !== null) {
$qb->setMaxResults($count);
}
$mailsToSend = $qb->getQuery()->execute();
/** @var Mail $mailToSend */
foreach ($mailsToSend as $mailToSend) {
try {
$this->sendMail(
$mailToSend->getTemplate(),
$mailToSend->getData(),
$mailToSend->getRecipient(),
$mailToSend->getSender(),
$mailToSend->getSenderName(),
);
$this->entityManager->remove($mailToSend);
} catch (SendMailFailedException $e) {
// log is done withing sendMail
}
$this->entityManager->flush();
}
} }
/** /**
* @throws SendMailFailedException * @throws SendMailFailedException
*/ */
public function send( private function sendMail(
string $template, string $template,
array $templateData, array $templateData,
string $recipient, string $recipient,
@ -112,9 +167,7 @@ class MailService
$defaultConfig = $this->configService->resolve('mail.default'); $defaultConfig = $this->configService->resolve('mail.default');
$from = $sender ?? $defaultConfig['sender'] ?? throw new Exception('Could not determine Sender'); $from = $sender ?? $defaultConfig['sender'] ?? throw new Exception('Could not determine Sender');
if ($senderName !== null) { $name =$sender ?? $defaultConfig['senderName'] ?? throw new Exception('Could not determine Sender Name');
$from = sprintf('%s <%s>', $senderName, $from); return sprintf('%s <%s>', $name, $from);
}
return $from;
} }
} }