Verified Commit 5c0192d4 authored by Korko's avatar Korko
Browse files

Use Queued Notification instead of Queued Job and Queued Mail

parent 3aa78b06
<?php
namespace App\Channels;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Notifications\Channels\MailChannel as BaseMailChannel;
use Illuminate\Notifications\Notification;
class MailChannel extends BaseMailChannel
{
/**
* Send the given notification.
*
* @param mixed $notifiable
* @param \Illuminate\Notifications\Notification $notification
* @return void
*/
public function send($notifiable, Notification $notification)
{
$message = $notification->toMail($notifiable);
// Fix bug in Laravel 8 about Mailable not being pushed in Mail::fake
if ($message instanceof Mailable) {
return $this->mailer->send($message);
}
return parent::send($notifiable, $notification);
}
}
\ No newline at end of file
......@@ -3,11 +3,10 @@
namespace App\Http\Controllers;
use App\Http\Requests\DearSantaRequest;
use App\Jobs\SendMail;
use App\Mail\DearSanta as DearSantaEmail;
use App\Models\DearSanta;
use App\Models\Mail as MailModel;
use App\Models\Participant;
use App\Notifications\DearSanta as DearSantaNotification;
use App\Traits\UpdatesMailDelivery;
class DearSantaController extends Controller
......@@ -58,7 +57,7 @@ class DearSantaController extends Controller
$participant->createMetric('dearsanta');
$this->sendMail($dearSanta);
$participant->santa->notify(new DearSantaNotification($dearSanta));
$message = trans('message.sent');
......@@ -70,9 +69,4 @@ class DearSantaController extends Controller
]) :
redirect('/dearsanta/'.$participant->hash)->with('message', $message);
}
protected function sendMail(DearSanta $dearSanta)
{
SendMail::dispatch($dearSanta->sender->santa, new DearSantaEmail($dearSanta));
}
}
......@@ -4,11 +4,10 @@ namespace App\Http\Controllers;
use App\Http\Requests\OrganizerChangeEmailRequest;
use App\Http\Requests\OrganizerResendEmailRequest;
use App\Jobs\SendMail;
use App\Mail\TargetDrawn;
use App\Models\Draw;
use App\Models\Mail as MailModel;
use App\Models\Participant;
use App\Notifications\TargetDrawn;
use Csv;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
......@@ -70,18 +69,13 @@ class OrganizerController extends Controller
$participant->mail->updateDeliveryStatus(MailModel::CREATED);
$this->sendEmail($participant);
$participant->notify(new TargetDrawn);
return response()->json([
'message' => $message, 'participant' => $participant->only(['id', 'mail']),
]);
}
protected function sendEmail(Participant $participant)
{
SendMail::dispatch($participant, new TargetDrawn($participant));
}
public function csvInit(Draw $draw)
{
$draw->createMetric('csv_initial_download');
......@@ -126,4 +120,4 @@ class OrganizerController extends Controller
'message' => trans('organizer.deleted'),
]);
}
}
}
\ No newline at end of file
......@@ -34,7 +34,7 @@ class RandomFormController extends Controller
DrawHandler::toParticipants($participants)
->expiresAt($dataExpiration)
->sendMail($title, $body);
->notify($title, $body);
}
protected function formatParticipants(array $participants): array
......
<?php
namespace App\Jobs;
use App\Models\Participant;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Mail;
use DrawCrypt;
class SendMail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $participant;
protected $mailable;
protected $key;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Participant $participant, Mailable $mailable)
{
$this->participant = $participant;
$this->mailable = $mailable;
$this->key = base64_encode(DrawCrypt::getKey());
}
public function getRecipient()
{
return $this->participant;
}
public function getMailable()
{
return $this->mailable;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
DrawCrypt::setKey(base64_decode($this->key));
Mail::to($this->participant)->send($this->mailable);
}
}
......@@ -3,12 +3,9 @@
namespace App\Mail;
use App\Models\DearSanta as DearSantaEntry;
use Illuminate\Bus\Queueable;
class DearSanta extends TrackedMailable
{
use Queueable;
protected $dearSanta;
public $targetName;
......
......@@ -5,13 +5,10 @@ namespace App\Mail;
use App;
use App\Models\Draw;
use DrawCrypt;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\URL;
class OrganizerRecap extends Mailable
{
use Queueable;
public $organizerName;
public $expirationDate;
public $deletionDate;
......
......@@ -4,13 +4,10 @@ namespace App\Mail;
use App\Models\Participant;
use DrawCrypt;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\URL;
class TargetDrawn extends TrackedMailable
{
use Queueable;
protected $santa;
public $content;
......
......@@ -29,6 +29,8 @@ class TrackedMailable extends Mailable
'mail' => $mail->hash,
'version' => $mail->version,
]);
return $this;
}
public function failed()
......
......@@ -9,11 +9,12 @@ use DB;
use exussum12\xxhash\V32 as xxHash;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Metrics;
class Draw extends Model
{
use HasFactory, HashId;
use HasFactory, HashId, Notifiable;
// Remove everything N weeks after the expiration_date
const WEEKS_BEFORE_DELETION = 3;
......
<?php
namespace App\Notifications;
use App\Channels\MailChannel;
use App\Models\DearSanta as DearSantaModel;
use App\Mail\DearSanta as DearSantaMailable;
use App\Models\Participant;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
//Illuminate/Contracts/Queue/ShouldBeEncrypted
class DearSanta extends Notification
{
use Queueable;
protected $dearSanta;
public function __construct(DearSantaModel $dearSanta)
{
$this->dearSanta = $dearSanta;
}
/**
* Get the notification's delivery channels.
*
* @param App\Models\Participant $santa
* @return array
*/
public function via(Participant $santa)
{
return [MailChannel::class];
}
/**
* Get the mail representation of the notification.
*
* @param App\Models\Participant $santa
* @return \Illuminate\Mail\Mailable
*/
public function toMail(Participant $santa)
{
return (new DearSantaMailable($this->dearSanta))
->to($santa);
}
}
<?php
namespace App\Notifications;
use App\Channels\MailChannel;
use App\Mail\OrganizerRecap as OrganizerRecapMailable;
use App\Models\Participant;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
//Illuminate/Contracts/Queue/ShouldBeEncrypted
class OrganizerRecap extends Notification
{
use Queueable;
/**
* Get the notification's delivery channels.
*
* @param App\Models\Participant $organizer
* @return array
*/
public function via(Participant $organizer)
{
return [MailChannel::class];
}
/**
* Get the mail representation of the notification.
*
* @param App\Models\Participant $organizer
* @return \Illuminate\Mail\Mailable
*/
public function toMail(Participant $organizer)
{
return (new OrganizerRecapMailable($organizer->draw))
->to($organizer);
}
}
<?php
namespace App\Notifications;
use App\Channels\MailChannel;
use App\Mail\SuggestRedraw as SuggestRedrawMailable;
use App\Models\Participant;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
//Illuminate/Contracts/Queue/ShouldBeEncrypted
class SuggestRedraw extends Notification
{
use Queueable;
/**
* Get the notification's delivery channels.
*
* @param App\Models\Participant $santa
* @return array
*/
public function via(Participant $santa)
{
return [MailChannel::class];
}
/**
* Get the mail representation of the notification.
*
* @param App\Models\Participant $santa
* @return \Illuminate\Mail\Mailable
*/
public function toMail(Participant $santa)
{
return (new SuggestRedrawMailable($santa))
->to($santa);
}
}
<?php
namespace App\Notifications;
use App\Channels\MailChannel;
use App\Mail\TargetDrawn as TargetDrawnMailable;
use App\Models\Participant;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
//Illuminate/Contracts/Queue/ShouldBeEncrypted
class TargetDrawn extends Notification
{
use Queueable;
/**
* Get the notification's delivery channels.
*
* @param App\Models\Participant $santa
* @return array
*/
public function via(Participant $santa)
{
return [MailChannel::class];
}
/**
* Get the mail representation of the notification.
*
* @param App\Models\Participant $santa
* @return \Illuminate\Mail\Mailable
*/
public function toMail(Participant $santa)
{
return (new TargetDrawnMailable($santa))
->to($santa);
}
}
......@@ -4,10 +4,9 @@ namespace App\Services;
use App\Models\Draw;
use App\Exceptions\SolverException;
use App\Jobs\SendMail;
use App\Models\Mail as MailModel;
use App\Mail\OrganizerRecap;
use App\Mail\TargetDrawn;
use App\Notifications\OrganizerRecap;
use App\Notifications\TargetDrawn;
use App\Models\Participant;
use Arr;
use Solver;
......@@ -33,7 +32,7 @@ class DrawHandler
return $this;
}
public function sendMail($title, $body): void
public function notify($title, $body): Draw
{
$draw = $this->createDraw($title, $body, $this->expirationDate ?: date('Y-m-d', strtotime('+2 days')));
$this->createParticipants($draw, $this->participants, $this->hat);
......@@ -46,10 +45,12 @@ class DrawHandler
$draw->save();
}
$this->sendOrganizerEmail($draw);
$draw->organizer->notify(new OrganizerRecap);
foreach ($draw->participants as $participant) {
$this->sendParticipantEmail($participant);
$participant->notify(new TargetDrawn);
}
return $draw;
}
public function createDraw($title, $body, $dataExpiration): Draw
......@@ -108,14 +109,4 @@ class DrawHandler
return false;
}
}
public function sendOrganizerEmail(Draw $draw)
{
SendMail::dispatch($draw->organizer, new OrganizerRecap($draw));
}
public function sendParticipantEmail(Participant $participant)
{
SendMail::dispatch($participant, new TargetDrawn($participant));
}
}
......@@ -7,21 +7,19 @@ use App\Models\Mail as MailModel;
trait UpdatesMailDelivery
{
public $mailId;
public $mail;
public $version;
public function store(MailModel $mail)
{
$this->mailId = $mail->id;
$this->mail = $mail;
$this->version = $mail->version;
}
public function delayedUpdateDelivery($status)
{
if (isset($this->mailId)) {
$mail = MailModel::find($this->mailId);
return $this->updateDelivery($mail, $status, $this->version);
if (isset($this->mail)) {
return $this->updateDelivery($this->mail->fresh(), $status, $this->version);
}
}
......
......@@ -21,8 +21,8 @@ it('does not record anything in case of error', function ($participants) {
})->with('invalid participants list');
it('does not send emails in case of error', function ($participants) {
Mail::fake();
Mail::assertNothingSent();
Notification::fake();
Notification::assertNothingSent();
try { DrawHandler::toParticipants($participants); } catch (Exception $e) {}
})->with('invalid participants list');
<?php
use App\Mail\OrganizerRecap;
use App\Mail\TargetDrawn;
use App\Mail\OrganizerRecap as OrganizerRecapMail;
use App\Notifications\OrganizerRecap as OrganizerRecapNotif;
use App\Notifications\TargetDrawn;
use App\Models\Draw;
use App\Models\Exclusion;
use App\Models\Participant;
function createDraw ($participants) {
DrawHandler::toParticipants($participants)
->expiresAt(date('Y-m-d', strtotime('+2 days')))
->sendMail('test mail {SANTA} => {TARGET} title', 'test mail {SANTA} => {TARGET} body');
}
it('records new entries in case of success', function ($participants) {
assertEquals(0, Draw::count());
assertEquals(0, Participant::count());
assertEquals(0, Exclusion::count());
createDraw($participants);
$draw = createDraw($participants);
$exclusions = array_reduce($participants, function ($sum, $participant) { return $sum + count($participant['exclusions']); });
assertEquals(1, Draw::count());
assertTrue($draw->is(Draw::find(1)));
assertEquals(count($participants), Participant::count());
assertEquals($exclusions, Exclusion::count());
// Carreful, array is 0..n, Db is 1..n
foreach($participants as $idx => $participant) {
assertEquals($participant['name'], Participant::find($idx + 1)->name);
assertEquals($participant['email'], Participant::find($idx + 1)->email);
}
})->with('valid participants list');
it('sends emails in case of success', function ($participants) {
Mail::fake();
it('sends notifications in case of success', function ($participants) {
Notification::fake();
createDraw($participants);
$draw = createDraw($participants);
// Ensure Organizer receives his recap
assertHasMailPushed(OrganizerRecap::class, $participants[0]['email']);
Notification::assertTimesSent(1, OrganizerRecapNotif::class);
Notification::assertSentTo($draw->organizer, OrganizerRecapNotif::class);
// Ensure Participants receive their own recap
foreach($participants as $participant) {
assertHasMailPushed(TargetDrawn::class, $participant['email']);
Notification::assertTimesSent(count($participants), TargetDrawn::class);
foreach($draw->participants as $participant) {
Notification::assertSentTo($participant, TargetDrawn::class);
}
})->with('valid participants list');
it('sends target name in emails', function ($participants, $targets) {
Mail::fake();
createDraw($participants);
it('saves the correct target', function ($participants, $targets) {
$draw = createDraw($participants);
// TODO: assert body
$title = null;
foreach($participants as $idx => $participant) {
assertHasMailPushed(TargetDrawn::class, $participant['email'], function ($m) use (&$title) {
$title = $m->subject;
});
assertStringContainsString('test mail '.$participant['name'].' => '.$participants[$targets[$idx]]['name'].' title', html_entity_decode($title));
assertEquals($participants[$targets[$idx]]['name'], $draw->participants[$idx]->target->name);
}
})->with('unique participants list');
it('stores database crypted and uncryptable entries', function ($participants) {
it('sends to the organizer the link to their panel', function ($participants) {
Mail::fake();
createDraw($participants);
// Ensure Organizer receives his recap
assertHasMailPushed(OrganizerRecap::class, 'test@test.com', function ($m) use (&$link) {
assertHasMailPushed(OrganizerRecapMail::class, $participants[0]['email'], function ($m) use (&$link) {
$link = $m->panelLink;
});
......@@ -78,4 +71,4 @@ it('stores database crypted and uncryptable entries', function ($participants) {
assertContains($participant->name, array_column($participants, 'name'));
assertContains($participant->email, array_column($participants, 'email'));
}
})->with('valid participants list');
})->with('valid participants list');
\ No newline at end of file
......@@ -7,7 +7,7 @@ test('the organizer can send again the target drawn email', function () {
Mail::fake();
$draw = createDrawWithParticipants(3);
$participant = $draw->participants->first();
$participant = $draw->participants->random();
// Check data can be changed
$path = URL::signedRoute('organizerPanel.changeEmail', [
......@@ -15,7 +15,10 @@ test('the organizer can send again the target drawn email', function () {
'participant' => $participant->id,
]);
ajaxPost($path, ['email' => $participant->email])->assertJsonStructure(['message'])->assertStatus(200);
ajaxPost($path, ['email' => $participant->email])
->assertJsonStructure(['message'])
->assertStatus(200);
assertHasMailPushed(TargetDrawn::class, $participant->email);
});
......@@ -23,7 +26,7 @@ test('the organizer can change a participant\'s email', function () {
Mail::fake();