Skip to content

Commit

Permalink
[Referrals] Send invitation email (#11521)
Browse files Browse the repository at this point in the history
* Send email after adhesion referral creation

* Referrals reports

* Add unit tests

* Fix tests

---------

Co-authored-by: Rémi <[email protected]>
Co-authored-by: Rémi <[email protected]>
  • Loading branch information
3 people authored Feb 26, 2025
1 parent 96abea7 commit e98e1e3
Show file tree
Hide file tree
Showing 19 changed files with 468 additions and 18 deletions.
77 changes: 70 additions & 7 deletions features/api/referrals.feature
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Feature:
"""
{
"metadata": {
"total_items": 2,
"total_items": 3,
"items_per_page": 10,
"count": 2,
"count": 3,
"current_page": 1,
"last_page": 1
},
Expand Down Expand Up @@ -67,6 +67,30 @@ Feature:
"type": "invitation",
"mode": "email",
"status": "invitation_sent"
},
{
"uuid": "34abd1e0-46e3-4c02-a4ad-8f632e03f7ce",
"email_address": "[email protected]",
"identifier": "@[email protected]('/^P[A-Z0-9]{5}$/')",
"first_name": "Jane",
"last_name": null,
"civility": null,
"birthdate": null,
"nationality": null,
"post_address": {
"additional_address": null,
"address": null,
"city": null,
"city_name": null,
"country": null,
"postal_code": null,
"region": null
},
"phone": null,
"referred": null,
"type": "invitation",
"mode": "email",
"status": "reported"
}
]
}
Expand All @@ -77,7 +101,7 @@ Feature:
And I send a "POST" request to "/api/v3/referrals" with body:
"""
{
"email_address": "jane.doe@dev.test",
"email_address": "new-email@dev.test",
"first_name": "Jane"
}
"""
Expand All @@ -88,7 +112,7 @@ Feature:
{
"uuid": "@uuid@",
"identifier": "@[email protected]('/^P[A-Z0-9]{5}$/')",
"email_address": "jane.doe@dev.test",
"email_address": "new-email@dev.test",
"first_name": "Jane",
"last_name": null,
"civility": null,
Expand All @@ -110,13 +134,52 @@ Feature:
"status": "invitation_sent"
}
"""
And I should have 1 email "ReferralAdhesionCreatedMessage" for "[email protected]" with payload:
"""
{
"template_name": "referral-adhesion-created",
"template_content": [],
"message": {
"subject": "Nouveau parrainage",
"from_email": "[email protected]",
"html": null,
"merge_vars": [
{
"rcpt": "[email protected]",
"vars": [
{
"content": "Jane",
"name": "first_name"
},
{
"content": "http://test.renaissance.code/invitation/adhesion/@string@",
"name": "adhesion_link"
},
{
"content": "http://test.renaissance.code/invitation/@string@/signaler",
"name": "report_link"
}
]
}
],
"from_name": "Renaissance",
"to": [
{
"email": "[email protected]",
"type": "to",
"name": "Jane"
}
]
}
}
"""

Scenario: As an logged in user, I can create a new referral with all informations
Given I am logged with "[email protected]" via OAuth client "JeMengage Mobile" with scope "jemarche_app"
And I send a "POST" request to "/api/v3/referrals" with body:
"""
{
"email_address": "jane.doe@dev.test",
"email_address": "new-email@dev.test",
"first_name": "Jane",
"last_name": "Doe",
"civility": "Madame",
Expand All @@ -138,7 +201,7 @@ Feature:
{
"uuid": "@uuid@",
"identifier": "@[email protected]('/^P[A-Z0-9]{5}$/')",
"email_address": "jane.doe@dev.test",
"email_address": "new-email@dev.test",
"first_name": "Jane",
"last_name": "Doe",
"civility": "Madame",
Expand Down Expand Up @@ -166,7 +229,7 @@ Feature:
And I send a "POST" request to "/api/v3/referrals" with body:
"""
{
"email_address": "jane.doe@dev.test",
"email_address": "new-email@dev.test",
"first_name": "Jane",
"last_name": "Doe"
}
Expand Down
19 changes: 19 additions & 0 deletions migrations/2025/Version20250220155540.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20250220155540 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('CREATE UNIQUE INDEX UNIQ_73079D00772E836A ON referral (identifier)');
}

public function down(Schema $schema): void
{
$this->addSql('DROP INDEX UNIQ_73079D00772E836A ON referral');
}
}
46 changes: 46 additions & 0 deletions src/Adherent/Referral/Notifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Adherent\Referral;

use App\Controller\Renaissance\Referral\AdhesionController;
use App\Controller\Renaissance\Referral\Report\FormController;
use App\Entity\Referral;
use App\Mailer\MailerService;
use App\Mailer\Message\Renaissance\Referral\ReferralAdhesionCreatedMessage;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class Notifier
{
public function __construct(
private readonly MailerService $transactionalMailer,
private readonly UrlGeneratorInterface $urlGenerator,
) {
}

public function sendAdhesionMessage(Referral $referral): void
{
$this->transactionalMailer->sendMessage(
ReferralAdhesionCreatedMessage::create(
$referral->emailAddress,
$referral->firstName,
$this->generateUrl(
AdhesionController::ROUTE_NAME,
[
'identifier' => $referral->identifier,
]
),
$this->generateUrl(
FormController::ROUTE_NAME,
[
'identifier' => $referral->identifier,
]
)
)
);
}

private function generateUrl(string $routeName, array $parameters = []): string
{
return $this->urlGenerator->generate($routeName, $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
}
}
20 changes: 20 additions & 0 deletions src/Adherent/Referral/ReportHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Adherent\Referral;

use App\Entity\Referral;
use Doctrine\ORM\EntityManagerInterface;

class ReportHandler
{
public function __construct(private readonly EntityManagerInterface $entityManager)
{
}

public function report(Referral $referral): void
{
$referral->report();

$this->entityManager->flush();
}
}
1 change: 1 addition & 0 deletions src/Adherent/Referral/StatusEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ enum StatusEnum: string
case ACCOUNT_CREATED = 'account_created';
case ADHESION_FINISHED = 'adhesion_finished';
case ADHESION_VIA_OTHER_LINK = 'adhesion_via_other_link';
case REPORTED = 'reported';
}
35 changes: 35 additions & 0 deletions src/Api/Listener/PostReferralCreateListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Api\Listener;

use ApiPlatform\Symfony\EventListener\EventPriorities;
use App\Adherent\Referral\Notifier;
use App\Entity\Referral;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class PostReferralCreateListener implements EventSubscriberInterface
{
public function __construct(private readonly Notifier $notifier)
{
}

public static function getSubscribedEvents(): array
{
return [KernelEvents::VIEW => ['onSubscribe', EventPriorities::POST_WRITE]];
}

public function onSubscribe(ViewEvent $viewEvent): void
{
$referral = $viewEvent->getControllerResult();

if (!$referral instanceof Referral) {
return;
}

if ($referral->isAdhesion()) {
$this->notifier->sendAdhesionMessage($referral);
}
}
}
25 changes: 25 additions & 0 deletions src/Controller/Renaissance/Referral/AdhesionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Controller\Renaissance\Referral;

use App\Entity\Referral;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/invitation/adhesion/{identifier}', name: self::ROUTE_NAME, requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET', 'POST'])]
class AdhesionController extends AbstractController
{
public const ROUTE_NAME = 'app_referral_adhesion';

public function __invoke(Referral $referral): Response
{
if (!$referral->isAdhesion()) {
throw $this->createNotFoundException();
}

return $this->render('renaissance/referral/adhesion.html.twig', [
'referral' => $referral,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Controller\Renaissance\Referral\Report;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/referral/report/confirmation', name: self::ROUTE_NAME, methods: ['GET'])]
class ConfirmationController extends AbstractController
{
public const ROUTE_NAME = 'app_referral_report_confirmation';

public function __invoke(): Response
{
return $this->render('renaissance/referral/report/confirmation.html.twig');
}
}
45 changes: 45 additions & 0 deletions src/Controller/Renaissance/Referral/Report/FormController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Controller\Renaissance\Referral\Report;

use App\Adherent\Referral\ReportHandler;
use App\Entity\Referral;
use App\Form\ConfirmActionType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/invitation/{identifier}/signaler', name: self::ROUTE_NAME, requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET', 'POST'])]
class FormController extends AbstractController
{
public const ROUTE_NAME = 'app_referral_report';

public function __construct(private readonly ReportHandler $reportHandler)
{
}

public function __invoke(Request $request, Referral $referral): Response
{
if ($referral->isReported()) {
return $this->render('renaissance/referral/report/already_reported.html.twig');
}

$form = $this
->createForm(ConfirmActionType::class, null, [
'with_deny' => false,
])
->handleRequest($request)
;

if ($form->isSubmitted() && $form->isValid()) {
$this->reportHandler->report($referral);

return $this->redirectToRoute('app_referral_report_confirmation');
}

return $this->render('renaissance/referral/report/form.html.twig', [
'form' => $form->createView(),
]);
}
}
Loading

0 comments on commit e98e1e3

Please sign in to comment.