Skip to content

Commit ad50f8c

Browse files
authored
Add outgoing message tracking (#459)
1 parent afcb4fe commit ad50f8c

13 files changed

+417
-41
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Generated;
4+
5+
/**
6+
* THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY.
7+
* @package HiEvents\DomainObjects\Generated
8+
*/
9+
abstract class OutgoingMessageDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject
10+
{
11+
final public const SINGULAR_NAME = 'outgoing_message';
12+
final public const PLURAL_NAME = 'outgoing_messages';
13+
final public const ID = 'id';
14+
final public const EVENT_ID = 'event_id';
15+
final public const MESSAGE_ID = 'message_id';
16+
final public const SUBJECT = 'subject';
17+
final public const RECIPIENT = 'recipient';
18+
final public const STATUS = 'status';
19+
final public const CREATED_AT = 'created_at';
20+
final public const UPDATED_AT = 'updated_at';
21+
final public const DELETED_AT = 'deleted_at';
22+
23+
protected int $id;
24+
protected int $event_id;
25+
protected int $message_id;
26+
protected string $subject;
27+
protected string $recipient;
28+
protected string $status;
29+
protected ?string $created_at = null;
30+
protected ?string $updated_at = null;
31+
protected ?string $deleted_at = null;
32+
33+
public function toArray(): array
34+
{
35+
return [
36+
'id' => $this->id ?? null,
37+
'event_id' => $this->event_id ?? null,
38+
'message_id' => $this->message_id ?? null,
39+
'subject' => $this->subject ?? null,
40+
'recipient' => $this->recipient ?? null,
41+
'status' => $this->status ?? null,
42+
'created_at' => $this->created_at ?? null,
43+
'updated_at' => $this->updated_at ?? null,
44+
'deleted_at' => $this->deleted_at ?? null,
45+
];
46+
}
47+
48+
public function setId(int $id): self
49+
{
50+
$this->id = $id;
51+
return $this;
52+
}
53+
54+
public function getId(): int
55+
{
56+
return $this->id;
57+
}
58+
59+
public function setEventId(int $event_id): self
60+
{
61+
$this->event_id = $event_id;
62+
return $this;
63+
}
64+
65+
public function getEventId(): int
66+
{
67+
return $this->event_id;
68+
}
69+
70+
public function setMessageId(int $message_id): self
71+
{
72+
$this->message_id = $message_id;
73+
return $this;
74+
}
75+
76+
public function getMessageId(): int
77+
{
78+
return $this->message_id;
79+
}
80+
81+
public function setSubject(string $subject): self
82+
{
83+
$this->subject = $subject;
84+
return $this;
85+
}
86+
87+
public function getSubject(): string
88+
{
89+
return $this->subject;
90+
}
91+
92+
public function setRecipient(string $recipient): self
93+
{
94+
$this->recipient = $recipient;
95+
return $this;
96+
}
97+
98+
public function getRecipient(): string
99+
{
100+
return $this->recipient;
101+
}
102+
103+
public function setStatus(string $status): self
104+
{
105+
$this->status = $status;
106+
return $this;
107+
}
108+
109+
public function getStatus(): string
110+
{
111+
return $this->status;
112+
}
113+
114+
public function setCreatedAt(?string $created_at): self
115+
{
116+
$this->created_at = $created_at;
117+
return $this;
118+
}
119+
120+
public function getCreatedAt(): ?string
121+
{
122+
return $this->created_at;
123+
}
124+
125+
public function setUpdatedAt(?string $updated_at): self
126+
{
127+
$this->updated_at = $updated_at;
128+
return $this;
129+
}
130+
131+
public function getUpdatedAt(): ?string
132+
{
133+
return $this->updated_at;
134+
}
135+
136+
public function setDeletedAt(?string $deleted_at): self
137+
{
138+
$this->deleted_at = $deleted_at;
139+
return $this;
140+
}
141+
142+
public function getDeletedAt(): ?string
143+
{
144+
return $this->deleted_at;
145+
}
146+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects;
4+
5+
class OutgoingMessageDomainObject extends Generated\OutgoingMessageDomainObjectAbstract
6+
{
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Status;
4+
5+
enum OutgoingMessageStatus
6+
{
7+
case SENT;
8+
case FAILED;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace HiEvents\Jobs\Event;
4+
5+
use HiEvents\DomainObjects\Generated\OutgoingMessageDomainObjectAbstract;
6+
use HiEvents\DomainObjects\Status\OutgoingMessageStatus;
7+
use HiEvents\Mail\Event\EventMessage;
8+
use HiEvents\Providers\AppServiceProvider;
9+
use HiEvents\Repository\Interfaces\OutgoingMessageRepositoryInterface;
10+
use HiEvents\Services\Application\Handlers\Message\DTO\SendMessageDTO;
11+
use Illuminate\Bus\Queueable;
12+
use Illuminate\Contracts\Queue\ShouldQueue;
13+
use Illuminate\Foundation\Bus\Dispatchable;
14+
use Illuminate\Mail\Mailer;
15+
use Illuminate\Queue\InteractsWithQueue;
16+
use Illuminate\Queue\Middleware\RateLimited;
17+
use Illuminate\Queue\SerializesModels;
18+
use Throwable;
19+
20+
class SendEventEmailJob implements ShouldQueue
21+
{
22+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
23+
24+
public function __construct(
25+
private readonly string $email,
26+
private readonly string $toName,
27+
private readonly EventMessage $eventMessage,
28+
private readonly SendMessageDTO $messageData,
29+
)
30+
{
31+
}
32+
33+
public function middleware(): array
34+
{
35+
return [
36+
(new RateLimited(AppServiceProvider::MAIL_RATE_LIMIT_PER_SECOND)),
37+
];
38+
}
39+
40+
/**
41+
* @throws Throwable
42+
*/
43+
public function handle(
44+
Mailer $mailer,
45+
OutgoingMessageRepositoryInterface $outgoingMessageRepository,
46+
): void
47+
{
48+
try {
49+
$mailer
50+
->to($this->email, $this->toName)
51+
->send($this->eventMessage);
52+
} catch (Throwable $exception) {
53+
$outgoingMessageRepository->create([
54+
OutgoingMessageDomainObjectAbstract::MESSAGE_ID => $this->messageData->id,
55+
OutgoingMessageDomainObjectAbstract::EVENT_ID => $this->messageData->event_id,
56+
OutgoingMessageDomainObjectAbstract::STATUS => OutgoingMessageStatus::FAILED->name,
57+
OutgoingMessageDomainObjectAbstract::RECIPIENT => $this->email,
58+
OutgoingMessageDomainObjectAbstract::SUBJECT => $this->messageData->subject,
59+
]);
60+
61+
throw $exception;
62+
}
63+
64+
$outgoingMessageRepository->create([
65+
OutgoingMessageDomainObjectAbstract::MESSAGE_ID => $this->messageData->id,
66+
OutgoingMessageDomainObjectAbstract::EVENT_ID => $this->messageData->event_id,
67+
OutgoingMessageDomainObjectAbstract::STATUS => OutgoingMessageStatus::SENT->name,
68+
OutgoingMessageDomainObjectAbstract::RECIPIENT => $this->email,
69+
OutgoingMessageDomainObjectAbstract::SUBJECT => $this->messageData->subject,
70+
]);
71+
}
72+
}

backend/app/Models/Message.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace HiEvents\Models;
44

5+
use Illuminate\Database\Eloquent\Relations\HasMany;
56
use Illuminate\Database\Eloquent\Relations\HasOne;
67
use Illuminate\Database\Eloquent\SoftDeletes;
78

@@ -14,6 +15,11 @@ public function sent_by_user(): HasOne
1415
return $this->hasOne(User::class, 'id', 'sent_by_user_id');
1516
}
1617

18+
public function outgoing_messages(): HasMany
19+
{
20+
return $this->hasMany(OutgoingMessage::class);
21+
}
22+
1723
protected function getCastMap(): array
1824
{
1925
return [
@@ -22,5 +28,4 @@ protected function getCastMap(): array
2228
'send_data' => 'array',
2329
];
2430
}
25-
2631
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace HiEvents\Models;
4+
5+
use Illuminate\Database\Eloquent\SoftDeletes;
6+
7+
class OutgoingMessage extends BaseModel
8+
{
9+
use SoftDeletes;
10+
}

backend/app/Providers/AppServiceProvider.php

+59-19
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@
99
use HiEvents\DomainObjects\OrganizerDomainObject;
1010
use HiEvents\Models\Event;
1111
use HiEvents\Models\Organizer;
12+
use Illuminate\Cache\RateLimiting\Limit;
13+
use Illuminate\Contracts\Queue\ShouldQueue;
1214
use Illuminate\Database\Eloquent\Model;
1315
use Illuminate\Database\Eloquent\Relations\Relation;
1416
use Illuminate\Support\Facades\DB;
1517
use Illuminate\Support\Facades\File;
18+
use Illuminate\Support\Facades\RateLimiter;
1619
use Illuminate\Support\Facades\URL;
1720
use Illuminate\Support\ServiceProvider;
1821
use Stripe\StripeClient;
1922

2023
class AppServiceProvider extends ServiceProvider
2124
{
25+
public const MAIL_RATE_LIMIT_PER_SECOND = 'mail-rate-limit-per-second';
26+
2227
public function register(): void
2328
{
2429
$this->bindDoctrineConnection();
@@ -30,28 +35,25 @@ public function register(): void
3035
*/
3136
public function boot(): void
3237
{
33-
if ($this->app->environment('local')) {
34-
URL::forceScheme('https');
35-
URL::forceRootUrl(config('app.url'));
36-
}
38+
$this->handleHttpsEnforcing();
3739

38-
if (env('APP_DEBUG') === true && env('APP_LOG_QUERIES') === true && !app()->isProduction()) {
39-
DB::listen(
40-
static function ($query) {
41-
File::append(
42-
storage_path('/logs/query.log'),
43-
$query->sql . ' [' . implode(', ', $query->bindings) . ']' . PHP_EOL
44-
);
45-
}
46-
);
47-
}
40+
$this->handleQueryLogging();
4841

49-
Model::preventLazyLoading(!app()->isProduction());
42+
$this->disableLazyLoading();
5043

51-
Relation::enforceMorphMap([
52-
EventDomainObject::class => Event::class,
53-
OrganizerDomainObject::class => Organizer::class,
54-
]);
44+
$this->registerMorphMaps();
45+
46+
$this->registerJobRateLimiters();
47+
}
48+
49+
private function registerJobRateLimiters(): void
50+
{
51+
RateLimiter::for(
52+
name: self::MAIL_RATE_LIMIT_PER_SECOND,
53+
callback: static fn(ShouldQueue $job) => Limit::perMinute(
54+
maxAttempts: config('mail.rate_limit_per_second')
55+
)
56+
);
5557
}
5658

5759
private function bindDoctrineConnection(): void
@@ -90,4 +92,42 @@ private function bindStripeClient(): void
9092
fn() => new StripeClient(config('services.stripe.secret_key'))
9193
);
9294
}
95+
96+
/**
97+
* @return void
98+
*/
99+
private function handleQueryLogging(): void
100+
{
101+
if (env('APP_DEBUG') === true && env('APP_LOG_QUERIES') === true && !app()->isProduction()) {
102+
DB::listen(
103+
static function ($query) {
104+
File::append(
105+
storage_path('/logs/query.log'),
106+
$query->sql . ' [' . implode(', ', $query->bindings) . ']' . PHP_EOL
107+
);
108+
}
109+
);
110+
}
111+
}
112+
113+
private function handleHttpsEnforcing(): void
114+
{
115+
if ($this->app->environment('local')) {
116+
URL::forceScheme('https');
117+
URL::forceRootUrl(config('app.url'));
118+
}
119+
}
120+
121+
private function registerMorphMaps(): void
122+
{
123+
Relation::enforceMorphMap([
124+
EventDomainObject::class => Event::class,
125+
OrganizerDomainObject::class => Organizer::class,
126+
]);
127+
}
128+
129+
private function disableLazyLoading(): void
130+
{
131+
Model::preventLazyLoading(!app()->isProduction());
132+
}
93133
}

0 commit comments

Comments
 (0)