diff --git a/README.md b/README.md
index 2ea6352a28..aadbea20b9 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,39 @@
- ๐ A star would be much appreciated! ๐
-
+
-
-
-
+๐ **Found Hi.Events helpful?**
+โญ Please consider giving us a star to support the project! โญ
-
Hi.Events
+
-
-Demo Event ๐ โข Website ๐ โข Documentation ๐ โข Installation โ๏ธ
+
+
-
- Open-source event management and ticketing platform.
-
-
-
+
Hi.Events
+Open-source event management and ticketing platform to sell tickets online for events of all sizes
[](https://www.addtoany.com/share?linkurl=https://github.com/HiEventsDev/hi.events)
[](https://x.com/HiEventsTickets)
-
[](https://hi.events/docs)
[](https://github.com/HiEventsDev/Hi.Events/blob/develop/LICENCE)
[](https://github.com/HiEventsDev/hi.events/releases)
[](https://github.com/HiEventsDev/hi.events/actions/workflows/unit-tests.yml)
[](https://hub.docker.com/r/daveearley/hi.events-all-in-one)
-
+
+Try Cloud โ๏ธ โข
+Demo Event ๐ โข
+Website ๐ โข
+Documentation ๐ โข
+Installation โ๏ธ
+
-
-

+
-
+
Deutsch |
Portuguรชs |
Franรงais |
@@ -44,85 +43,58 @@
-## Table of Contents
-
-- [Introduction](#-introduction)
-- [Features](#-features)
-- [Getting Started](#-getting-started)
-- [Change Log](#-change-log)
-- [Contributing](#-contributing)
-- [FAQ](#-faq)
-
## ๐ Introduction
-Hi.Events is a feature-rich, self-hosted event management and ticketing platform. From conferences to club nights,
-Hi.Events is designed to help you create, manage, and sell tickets for events of all sizes.
+Hi.Events is a feature-rich, self-hosted event management and ticketing platform that helps you sell tickets online for all types of events. From conferences and workshops to club nights and concerts, Hi.Events provides everything you need to create, manage, and monetize your events with ease.
+
-## ๐ Features
-
-Hi.Events is packed with features to streamline your event management and ticketing:
-
-### ๐ Ticketing & Product Sales
-- **Multiple Ticket Types:** Free, Paid, Donation, and Tiered tickets.
-- **Capacity Management:** Set event-wide or ticket-specific limits.
-- **Capacity Assignments:** Manage shared capacity across multiple ticket types.
-- **Promo Codes:** Discount codes for pre-sale access and special offers.
-- **Product Sales:** Sell event-related products (e.g., t-shirts, add-ons).
-- **Taxes & Fees:** Apply custom taxes and fees per product or order.
-
-### ๐ Event Management & Customization
-- **Event Dashboard:** Real-time revenue, ticket sales, and attendee analytics.
-- **Homepage Designer:** Customize event pages with a live preview editor.
-- **Embeddable Ticket Widget:** Add a seamless ticketing experience to your website.
-- **SEO Tools:** Customize event metadata for better search visibility.
-- **Product Categories:** Organize products and tickets with category management.
-- **Offline Event Support:** Provide instructions for physical events.
-
-### ๐ง Attendee & Order Management
-- **Custom Checkout Forms:** Collect attendee details with tailored questions.
-- **Attendee Management:** Search, edit, cancel, and message attendees.
-- **Order Management:** Refund, cancel, and resend order details easily.
-- **Bulk Messaging:** Email or message specific ticket holders.
-- **Data Exports:** Export attendees and orders to CSV/XLSX.
-
-### ๐ฑ Mobile-Friendly & Check-In Tools
-- **QR Code Check-In:** Web-based and mobile-friendly check-in tool.
-- **Check-In Lists:** Generate and share access-controlled check-in lists.
-- **Multi-User Access:** Role-based access control for event staff.
-
-### ๐ง Integrations & Automation
-- **Webhooks Support:** Automate tasks with Zapier, IFTTT, Make, or CRM integrations.
-- **Stripe Connect Integration:** Organizers get instant payouts.
-
-### ๐ Advanced Features
-- **Multi-Language Support:** English, Deutsch, Espaรฑol, Portuguรชs, Franรงais, ไธญๆ (Zhลngwรฉn), and more.
-- **Partial & Full Refunds:** Manage refunds with detailed order tracking.
-- **Role-Based Access Control:** Multiple user roles with permission management.
-- **REST API:** Full API access for custom integrations.
-- **Invoicing System:** Generate and send invoices with tax details, payment terms, and due dates.
-- **Offline Payment Support:** Enable bank transfers, cash payments, or custom payment methods.
-- **Event Archive:** Archive past events to keep the dashboard organized.
-- **Advanced Ticket Locking:** Lock tickets behind promo codes or access restrictions.
-- **Advanced Reporting:** Daily sales, tax breakdowns, product sales, and promo code usage reports.
-
-## ๐ Getting Started
-
-For detailed installation instructions, please refer to our [documentation](https://hi.events/docs/getting-started). For
-a quick start, follow these steps:
+## โก Quick Deploy
-### One-Click Deployments
+Get started in minutes with our one-click deployment options:
[](https://github.com/HiEventsDev/hi.events-digitalocean)
-
[](https://github.com/HiEventsDev/hi.events-render.com)
-
[](https://railway.app/template/8CGKmu?referralCode=KvSr11)
-
[](https://zeabur.com/templates/8DIRY6)
+## ๐ Key Features
+
+Hi.Events offers comprehensive tools to streamline your event management:
+
+### ๐ Ticketing & Sales
+- **Multiple Ticket Types:** Create free, paid, donation-based, and tiered tickets
+- **Capacity Management:** Set limits per event or ticket type
+- **Promo Codes & Discounts:** Drive early sales with special offers
+- **Product Upsells:** Sell merchandise and add-ons alongside tickets
+- **Custom Pricing:** Apply taxes and fees per product or entire order
+
+### ๐ Event Management
+- **Real-time Dashboard:** Track sales, revenue, and attendee metrics
+- **Visual Page Editor:** Design beautiful event pages with live preview
+- **Website Integration:** Embed ticketing widgets on your existing site
+- **SEO Optimization:** Customize metadata for better search visibility
+- **Offline Event Support:** Provide location details and instructions
+
+### ๐ฑ Attendee Experience
+- **Custom Registration Forms:** Collect exactly the information you need
+- **QR Code Check-In:** Fast, mobile-friendly entry verification
+- **Multi-language Support:** Reach global audiences with localized interfaces
+- **Bulk Communication:** Send targeted messages to specific ticket holders
+- **Refund Management:** Process full or partial refunds when needed
+
+### ๐ง For Organizers
+- **Team Collaboration:** Role-based access for staff members
+- **Webhook Integration:** Connect with Zapier, IFTTT, Make, or your CRM
+- **Stripe Connect:** Receive instant payouts for ticket sales
+- **Comprehensive API:** Build custom integrations with full API access
+- **Advanced Reporting:** Generate sales, tax, and usage reports
+
+## ๐ Getting Started
+
### ๐ณ Quick Start with Docker
> [!IMPORTANT]
@@ -148,7 +120,7 @@ a quick start, follow these steps:
echo base64:$(openssl rand -base64 32) # For APP_KEY
openssl rand -base64 32 # For JWT_SECRET
```
-
+
**Windows:**
Check the instructions in *./docker/all-in-one/README.md* for generating the keys on Windows.
@@ -159,48 +131,36 @@ a quick start, follow these steps:
docker compose up -d
```
5. **Create an account:**
- ```bash
- Open your browser and navigate to http://localhost:8123/auth/register.
+ ```
+ Open your browser and navigate to http://localhost:8123/auth/register
```
-โน๏ธ Please refer to the [getting started guide](https://hi.events/docs/getting-started) for other installation methods, and
-for setting up a production or local development environment.
+โน๏ธ For detailed setup instructions including production deployment, please refer to our [getting started guide](https://hi.events/docs/getting-started).
## ๐ Sponsors
-
-Stinking Badges
-
-### Making a Donation
+### Support the Project
-If you find Hi.Events useful, it would be massively appreciated if you made a small donation to help support the project.
+If you find Hi.Events valuable for your organization, please consider supporting ongoing development:
-We'll use your donation to fund ongoing development and maintenance of Hi.Events.
+
-
-
-or
- Sponsor on GitHub
-or
- Sponsor on Open Collective
+Or support us on: GitHub Sponsors | Open Collective
## ๐ Change Log
-Stay updated with our ongoing improvements and feature additions at
-our [GitHub releases page](https://github.com/HiEventsDev/hi.events/releases).
+Stay updated with our latest features and improvements on our [GitHub releases page](https://github.com/HiEventsDev/hi.events/releases).
## ๐ค Contributing
-We welcome contributions, suggestions, and bug reports! Please see our [contributing guidelines](CONTRIBUTING.md) for more
-information.
+We welcome contributions from the community! Please see our [contributing guidelines](CONTRIBUTING.md) for details on how to get involved.
## โ FAQ
-Have questions? Our [Docs](https://hi.events/docs) have answers. If you can't find what you're looking for, feel free to
-reach out to us at [hello@hi.events](mailto:hello@hi.events).
+Have questions? Our [documentation](https://hi.events/docs?utm_source=gh-readme&utm_content=faq-docs-link) has answers. For additional support, contact us at [hello@hi.events](mailto:hello@hi.events).
## ๐ License
-Hi.Events is licensed under the terms of the [AGPL-3.0](https://github.com/HiEventsDev/hi.events/blob/main/LICENCE) license.
+Hi.Events is licensed under the [AGPL-3.0](https://github.com/HiEventsDev/hi.events/blob/main/LICENCE) license.
For more licensing information, including commercial licencing options, please visit our licensing page [here](https://hi.events/licensing).
diff --git a/backend/app/DomainObjects/Generated/OutgoingMessageDomainObjectAbstract.php b/backend/app/DomainObjects/Generated/OutgoingMessageDomainObjectAbstract.php
new file mode 100644
index 0000000000..8375d808fd
--- /dev/null
+++ b/backend/app/DomainObjects/Generated/OutgoingMessageDomainObjectAbstract.php
@@ -0,0 +1,146 @@
+ $this->id ?? null,
+ 'event_id' => $this->event_id ?? null,
+ 'message_id' => $this->message_id ?? null,
+ 'subject' => $this->subject ?? null,
+ 'recipient' => $this->recipient ?? null,
+ 'status' => $this->status ?? null,
+ 'created_at' => $this->created_at ?? null,
+ 'updated_at' => $this->updated_at ?? null,
+ 'deleted_at' => $this->deleted_at ?? null,
+ ];
+ }
+
+ public function setId(int $id): self
+ {
+ $this->id = $id;
+ return $this;
+ }
+
+ public function getId(): int
+ {
+ return $this->id;
+ }
+
+ public function setEventId(int $event_id): self
+ {
+ $this->event_id = $event_id;
+ return $this;
+ }
+
+ public function getEventId(): int
+ {
+ return $this->event_id;
+ }
+
+ public function setMessageId(int $message_id): self
+ {
+ $this->message_id = $message_id;
+ return $this;
+ }
+
+ public function getMessageId(): int
+ {
+ return $this->message_id;
+ }
+
+ public function setSubject(string $subject): self
+ {
+ $this->subject = $subject;
+ return $this;
+ }
+
+ public function getSubject(): string
+ {
+ return $this->subject;
+ }
+
+ public function setRecipient(string $recipient): self
+ {
+ $this->recipient = $recipient;
+ return $this;
+ }
+
+ public function getRecipient(): string
+ {
+ return $this->recipient;
+ }
+
+ public function setStatus(string $status): self
+ {
+ $this->status = $status;
+ return $this;
+ }
+
+ public function getStatus(): string
+ {
+ return $this->status;
+ }
+
+ public function setCreatedAt(?string $created_at): self
+ {
+ $this->created_at = $created_at;
+ return $this;
+ }
+
+ public function getCreatedAt(): ?string
+ {
+ return $this->created_at;
+ }
+
+ public function setUpdatedAt(?string $updated_at): self
+ {
+ $this->updated_at = $updated_at;
+ return $this;
+ }
+
+ public function getUpdatedAt(): ?string
+ {
+ return $this->updated_at;
+ }
+
+ public function setDeletedAt(?string $deleted_at): self
+ {
+ $this->deleted_at = $deleted_at;
+ return $this;
+ }
+
+ public function getDeletedAt(): ?string
+ {
+ return $this->deleted_at;
+ }
+}
diff --git a/backend/app/DomainObjects/OutgoingMessageDomainObject.php b/backend/app/DomainObjects/OutgoingMessageDomainObject.php
new file mode 100644
index 0000000000..25d613fd46
--- /dev/null
+++ b/backend/app/DomainObjects/OutgoingMessageDomainObject.php
@@ -0,0 +1,7 @@
+ 'required|url',
- 'event_types.*' => ['required', Rule::in(WebhookEventType::valuesArray())],
+ 'event_types.*' => ['required', Rule::in(DomainEventType::valuesArray())],
'status' => ['nullable', Rule::in(WebhookStatus::valuesArray())],
];
}
diff --git a/backend/app/Jobs/Event/SendEventEmailJob.php b/backend/app/Jobs/Event/SendEventEmailJob.php
new file mode 100644
index 0000000000..737c885d77
--- /dev/null
+++ b/backend/app/Jobs/Event/SendEventEmailJob.php
@@ -0,0 +1,72 @@
+to($this->email, $this->toName)
+ ->send($this->eventMessage);
+ } catch (Throwable $exception) {
+ $outgoingMessageRepository->create([
+ OutgoingMessageDomainObjectAbstract::MESSAGE_ID => $this->messageData->id,
+ OutgoingMessageDomainObjectAbstract::EVENT_ID => $this->messageData->event_id,
+ OutgoingMessageDomainObjectAbstract::STATUS => OutgoingMessageStatus::FAILED->name,
+ OutgoingMessageDomainObjectAbstract::RECIPIENT => $this->email,
+ OutgoingMessageDomainObjectAbstract::SUBJECT => $this->messageData->subject,
+ ]);
+
+ throw $exception;
+ }
+
+ $outgoingMessageRepository->create([
+ OutgoingMessageDomainObjectAbstract::MESSAGE_ID => $this->messageData->id,
+ OutgoingMessageDomainObjectAbstract::EVENT_ID => $this->messageData->event_id,
+ OutgoingMessageDomainObjectAbstract::STATUS => OutgoingMessageStatus::SENT->name,
+ OutgoingMessageDomainObjectAbstract::RECIPIENT => $this->email,
+ OutgoingMessageDomainObjectAbstract::SUBJECT => $this->messageData->subject,
+ ]);
+ }
+}
diff --git a/backend/app/Jobs/Order/Webhook/DispatchAttendeeWebhookJob.php b/backend/app/Jobs/Order/Webhook/DispatchAttendeeWebhookJob.php
index fc885634f9..e7c308047c 100644
--- a/backend/app/Jobs/Order/Webhook/DispatchAttendeeWebhookJob.php
+++ b/backend/app/Jobs/Order/Webhook/DispatchAttendeeWebhookJob.php
@@ -2,7 +2,7 @@
namespace HiEvents\Jobs\Order\Webhook;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -14,8 +14,8 @@ class DispatchAttendeeWebhookJob
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
- public int $attendeeId,
- public WebhookEventType $eventType,
+ public int $attendeeId,
+ public DomainEventType $eventType,
)
{
}
diff --git a/backend/app/Jobs/Order/Webhook/DispatchCheckInWebhookJob.php b/backend/app/Jobs/Order/Webhook/DispatchCheckInWebhookJob.php
index db5ce766b4..02db366898 100644
--- a/backend/app/Jobs/Order/Webhook/DispatchCheckInWebhookJob.php
+++ b/backend/app/Jobs/Order/Webhook/DispatchCheckInWebhookJob.php
@@ -2,7 +2,7 @@
namespace HiEvents\Jobs\Order\Webhook;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -14,8 +14,8 @@ class DispatchCheckInWebhookJob
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
- public int $attendeeCheckInId,
- public WebhookEventType $eventType,
+ public int $attendeeCheckInId,
+ public DomainEventType $eventType,
)
{
}
diff --git a/backend/app/Jobs/Order/Webhook/DispatchOrderWebhookJob.php b/backend/app/Jobs/Order/Webhook/DispatchOrderWebhookJob.php
index fa9aa4533c..e0bca0737e 100644
--- a/backend/app/Jobs/Order/Webhook/DispatchOrderWebhookJob.php
+++ b/backend/app/Jobs/Order/Webhook/DispatchOrderWebhookJob.php
@@ -2,7 +2,7 @@
namespace HiEvents\Jobs\Order\Webhook;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -14,8 +14,8 @@ class DispatchOrderWebhookJob
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
- public int $orderId,
- public WebhookEventType $eventType,
+ public int $orderId,
+ public DomainEventType $eventType,
)
{
}
diff --git a/backend/app/Jobs/Order/Webhook/DispatchProductWebhookJob.php b/backend/app/Jobs/Order/Webhook/DispatchProductWebhookJob.php
index 12e70eb65f..978b877a89 100644
--- a/backend/app/Jobs/Order/Webhook/DispatchProductWebhookJob.php
+++ b/backend/app/Jobs/Order/Webhook/DispatchProductWebhookJob.php
@@ -2,7 +2,7 @@
namespace HiEvents\Jobs\Order\Webhook;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -14,8 +14,8 @@ class DispatchProductWebhookJob
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
- public int $productId,
- public WebhookEventType $eventType,
+ public int $productId,
+ public DomainEventType $eventType,
)
{
}
diff --git a/backend/app/Listeners/Webhook/WebhookEventListener.php b/backend/app/Listeners/Webhook/WebhookEventListener.php
new file mode 100644
index 0000000000..c3f93cfd43
--- /dev/null
+++ b/backend/app/Listeners/Webhook/WebhookEventListener.php
@@ -0,0 +1,55 @@
+config->get('queue.webhook_queue_name');
+
+ switch (get_class($event)) {
+ case AttendeeEvent::class:
+ DispatchAttendeeWebhookJob::dispatch(
+ attendeeId: $event->attendeeId,
+ eventType: $event->type,
+ )->onQueue($queueName);
+ break;
+ case OrderEvent::class:
+ DispatchOrderWebhookJob::dispatch(
+ orderId: $event->orderId,
+ eventType: $event->type,
+ )->onQueue($queueName);
+ break;
+ case ProductEvent::class:
+ DispatchProductWebhookJob::dispatch(
+ productId: $event->productId,
+ eventType: $event->type,
+ )->onQueue($queueName);
+ break;
+ case CheckinEvent::class:
+ DispatchCheckInWebhookJob::dispatch(
+ attendeeCheckInId: $event->attendeeCheckinId,
+ eventType: $event->type,
+ )->onQueue($queueName);
+ break;
+ }
+ }
+}
diff --git a/backend/app/Models/Message.php b/backend/app/Models/Message.php
index c9e1e058b2..3680e2a19b 100644
--- a/backend/app/Models/Message.php
+++ b/backend/app/Models/Message.php
@@ -2,6 +2,7 @@
namespace HiEvents\Models;
+use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -14,6 +15,11 @@ public function sent_by_user(): HasOne
return $this->hasOne(User::class, 'id', 'sent_by_user_id');
}
+ public function outgoing_messages(): HasMany
+ {
+ return $this->hasMany(OutgoingMessage::class);
+ }
+
protected function getCastMap(): array
{
return [
@@ -22,5 +28,4 @@ protected function getCastMap(): array
'send_data' => 'array',
];
}
-
}
diff --git a/backend/app/Models/OutgoingMessage.php b/backend/app/Models/OutgoingMessage.php
new file mode 100644
index 0000000000..c857f7e96b
--- /dev/null
+++ b/backend/app/Models/OutgoingMessage.php
@@ -0,0 +1,10 @@
+bindDoctrineConnection();
@@ -30,28 +35,25 @@ public function register(): void
*/
public function boot(): void
{
- if ($this->app->environment('local')) {
- URL::forceScheme('https');
- URL::forceRootUrl(config('app.url'));
- }
+ $this->handleHttpsEnforcing();
- if (env('APP_DEBUG') === true && env('APP_LOG_QUERIES') === true && !app()->isProduction()) {
- DB::listen(
- static function ($query) {
- File::append(
- storage_path('/logs/query.log'),
- $query->sql . ' [' . implode(', ', $query->bindings) . ']' . PHP_EOL
- );
- }
- );
- }
+ $this->handleQueryLogging();
- Model::preventLazyLoading(!app()->isProduction());
+ $this->disableLazyLoading();
- Relation::enforceMorphMap([
- EventDomainObject::class => Event::class,
- OrganizerDomainObject::class => Organizer::class,
- ]);
+ $this->registerMorphMaps();
+
+ $this->registerJobRateLimiters();
+ }
+
+ private function registerJobRateLimiters(): void
+ {
+ RateLimiter::for(
+ name: self::MAIL_RATE_LIMIT_PER_SECOND,
+ callback: static fn(ShouldQueue $job) => Limit::perMinute(
+ maxAttempts: config('mail.rate_limit_per_second')
+ )
+ );
}
private function bindDoctrineConnection(): void
@@ -90,4 +92,42 @@ private function bindStripeClient(): void
fn() => new StripeClient(config('services.stripe.secret_key'))
);
}
+
+ /**
+ * @return void
+ */
+ private function handleQueryLogging(): void
+ {
+ if (env('APP_DEBUG') === true && env('APP_LOG_QUERIES') === true && !app()->isProduction()) {
+ DB::listen(
+ static function ($query) {
+ File::append(
+ storage_path('/logs/query.log'),
+ $query->sql . ' [' . implode(', ', $query->bindings) . ']' . PHP_EOL
+ );
+ }
+ );
+ }
+ }
+
+ private function handleHttpsEnforcing(): void
+ {
+ if ($this->app->environment('local')) {
+ URL::forceScheme('https');
+ URL::forceRootUrl(config('app.url'));
+ }
+ }
+
+ private function registerMorphMaps(): void
+ {
+ Relation::enforceMorphMap([
+ EventDomainObject::class => Event::class,
+ OrganizerDomainObject::class => Organizer::class,
+ ]);
+ }
+
+ private function disableLazyLoading(): void
+ {
+ Model::preventLazyLoading(!app()->isProduction());
+ }
}
diff --git a/backend/app/Providers/EventServiceProvider.php b/backend/app/Providers/EventServiceProvider.php
index 338991f453..9d94df60a2 100644
--- a/backend/app/Providers/EventServiceProvider.php
+++ b/backend/app/Providers/EventServiceProvider.php
@@ -2,20 +2,27 @@
namespace HiEvents\Providers;
-use Illuminate\Auth\Events\Registered;
-use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
+use HiEvents\Listeners\Webhook\WebhookEventListener;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\AttendeeEvent;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\CheckinEvent;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\ProductEvent;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
+use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
- * The event to listener mappings for the application.
+ * Map of listeners to the events they should handle.
*
- * @var array>
+ * @var array>
*/
- protected $listen = [
- Registered::class => [
- SendEmailVerificationNotification::class,
+ private static array $domainEventMap = [
+ WebhookEventListener::class => [
+ ProductEvent::class,
+ OrderEvent::class,
+ AttendeeEvent::class,
+ CheckinEvent::class,
],
];
@@ -24,7 +31,19 @@ class EventServiceProvider extends ServiceProvider
*/
public function boot(): void
{
- //
+ $this->registerDomainEventListeners();
+ }
+
+ /**
+ * Dynamically register all domain event listeners.
+ */
+ private function registerDomainEventListeners(): void
+ {
+ foreach (self::$domainEventMap as $listener => $events) {
+ foreach ($events as $event) {
+ Event::listen($event, [$listener, 'handle']);
+ }
+ }
}
/**
diff --git a/backend/app/Providers/RepositoryServiceProvider.php b/backend/app/Providers/RepositoryServiceProvider.php
index 18cb630db4..573d64c601 100644
--- a/backend/app/Providers/RepositoryServiceProvider.php
+++ b/backend/app/Providers/RepositoryServiceProvider.php
@@ -23,6 +23,7 @@
use HiEvents\Repository\Eloquent\OrderRefundRepository;
use HiEvents\Repository\Eloquent\OrderRepository;
use HiEvents\Repository\Eloquent\OrganizerRepository;
+use HiEvents\Repository\Eloquent\OutgoingMessageRepository;
use HiEvents\Repository\Eloquent\PasswordResetRepository;
use HiEvents\Repository\Eloquent\PasswordResetTokenRepository;
use HiEvents\Repository\Eloquent\ProductCategoryRepository;
@@ -57,6 +58,7 @@
use HiEvents\Repository\Interfaces\OrderRefundRepositoryInterface;
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Repository\Interfaces\OrganizerRepositoryInterface;
+use HiEvents\Repository\Interfaces\OutgoingMessageRepositoryInterface;
use HiEvents\Repository\Interfaces\PasswordResetRepositoryInterface;
use HiEvents\Repository\Interfaces\PasswordResetTokenRepositoryInterface;
use HiEvents\Repository\Interfaces\ProductCategoryRepositoryInterface;
@@ -114,6 +116,7 @@ class RepositoryServiceProvider extends ServiceProvider
OrderApplicationFeeRepositoryInterface::class => OrderApplicationFeeRepository::class,
AccountConfigurationRepositoryInterface::class => AccountConfigurationRepository::class,
QuestionAndAnswerViewRepositoryInterface::class => QuestionAndAnswerViewRepository::class,
+ OutgoingMessageRepositoryInterface::class => OutgoingMessageRepository::class,
];
public function register(): void
diff --git a/backend/app/Repository/Eloquent/BaseRepository.php b/backend/app/Repository/Eloquent/BaseRepository.php
index b5f0e1a20e..0a3364b71d 100644
--- a/backend/app/Repository/Eloquent/BaseRepository.php
+++ b/backend/app/Repository/Eloquent/BaseRepository.php
@@ -96,7 +96,7 @@ public function paginateWhere(
public function simplePaginateWhere(
array $where,
- int $limit = null,
+ ?int $limit = null,
array $columns = self::DEFAULT_COLUMNS,
): Paginator
{
@@ -126,7 +126,7 @@ public function findById(int $id, array $columns = self::DEFAULT_COLUMNS): Domai
public function findFirstByField(
string $field,
- string $value = null,
+ ?string $value = null,
array $columns = ['*']
): ?DomainObjectInterface
{
diff --git a/backend/app/Repository/Eloquent/OutgoingMessageRepository.php b/backend/app/Repository/Eloquent/OutgoingMessageRepository.php
new file mode 100644
index 0000000000..b977237a60
--- /dev/null
+++ b/backend/app/Repository/Eloquent/OutgoingMessageRepository.php
@@ -0,0 +1,20 @@
+
+ */
+interface OutgoingMessageRepositoryInterface extends RepositoryInterface
+{
+
+}
diff --git a/backend/app/Repository/Interfaces/RepositoryInterface.php b/backend/app/Repository/Interfaces/RepositoryInterface.php
index 0b5930bc7e..13783497f2 100644
--- a/backend/app/Repository/Interfaces/RepositoryInterface.php
+++ b/backend/app/Repository/Interfaces/RepositoryInterface.php
@@ -71,7 +71,7 @@ public function paginateWhere(
*/
public function simplePaginateWhere(
array $where,
- int $limit = null,
+ ?int $limit = null,
array $columns = self::DEFAULT_COLUMNS,
): Paginator;
@@ -129,7 +129,7 @@ public function findFirstWhere(array $where, array $columns = self::DEFAULT_COLU
*/
public function findFirstByField(
string $field,
- string $value = null,
+ ?string $value = null,
array $columns = ['*']
): ?DomainObjectInterface;
diff --git a/backend/app/Resources/Image/ImageResource.php b/backend/app/Resources/Image/ImageResource.php
index f7d896e38a..26c4cbaf5d 100644
--- a/backend/app/Resources/Image/ImageResource.php
+++ b/backend/app/Resources/Image/ImageResource.php
@@ -16,6 +16,7 @@ public function toArray($request): array
return [
'id' => $this->getId(),
'url' => Url::getCdnUrl($this->getPath()),
+ 'path' => $this->getPath(),
'size' => $this->getSize(),
'file_name' => $this->getFileName(),
'mime_type' => $this->getMimeType(),
diff --git a/backend/app/Services/Application/Handlers/Attendee/CreateAttendeeHandler.php b/backend/app/Services/Application/Handlers/Attendee/CreateAttendeeHandler.php
index 8ccd56d596..4ca0d841a0 100644
--- a/backend/app/Services/Application/Handlers/Attendee/CreateAttendeeHandler.php
+++ b/backend/app/Services/Application/Handlers/Attendee/CreateAttendeeHandler.php
@@ -5,7 +5,6 @@
use Brick\Money\Money;
use HiEvents\DomainObjects\AttendeeDomainObject;
use HiEvents\DomainObjects\Enums\ProductType;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\OrderItemDomainObjectAbstract;
@@ -31,7 +30,9 @@
use HiEvents\Services\Domain\Order\OrderManagementService;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
use HiEvents\Services\Domain\Tax\TaxAndFeeRollupService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Collection;
use RuntimeException;
@@ -49,7 +50,7 @@ public function __construct(
private readonly TaxAndFeeRepositoryInterface $taxAndFeeRepository,
private readonly TaxAndFeeRollupService $taxAndFeeRollupService,
private readonly OrderManagementService $orderManagementService,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -103,7 +104,7 @@ public function handle(CreateAttendeeDTO $attendeeDTO): AttendeeDomainObject
$this->fireEventsAndUpdateQuantities($attendeeDTO, $order);
- $this->queueWebhooks($attendee, $order);
+ $this->queueWebhooks($order);
return $attendee;
});
@@ -247,11 +248,10 @@ private function fireEventsAndUpdateQuantities(CreateAttendeeDTO $attendeeDTO, O
));
}
- private function queueWebhooks(AttendeeDomainObject $attendee, OrderDomainObject $order): void
+ private function queueWebhooks(OrderDomainObject $order): void
{
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_CREATED,
- orderId: $order->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(DomainEventType::ORDER_CREATED, $order->getId())
);
}
}
diff --git a/backend/app/Services/Application/Handlers/Attendee/EditAttendeeHandler.php b/backend/app/Services/Application/Handlers/Attendee/EditAttendeeHandler.php
index 08d197adac..9a714d2a80 100644
--- a/backend/app/Services/Application/Handlers/Attendee/EditAttendeeHandler.php
+++ b/backend/app/Services/Application/Handlers/Attendee/EditAttendeeHandler.php
@@ -4,7 +4,6 @@
use HiEvents\DomainObjects\AttendeeDomainObject;
use HiEvents\DomainObjects\Enums\ProductPriceType;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract;
use HiEvents\DomainObjects\ProductDomainObject;
@@ -14,7 +13,9 @@
use HiEvents\Repository\Interfaces\ProductRepositoryInterface;
use HiEvents\Services\Application\Handlers\Attendee\DTO\EditAttendeeDTO;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\AttendeeEvent;
use Illuminate\Database\DatabaseManager;
use Illuminate\Validation\ValidationException;
use Throwable;
@@ -26,7 +27,7 @@ public function __construct(
private readonly ProductRepositoryInterface $productRepository,
private readonly ProductQuantityUpdateService $productQuantityService,
private readonly DatabaseManager $databaseManager,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -46,9 +47,11 @@ public function handle(EditAttendeeDTO $editAttendeeDTO): AttendeeDomainObject
$updatedAttendee = $this->updateAttendee($editAttendeeDTO);
- $this->webhookDispatchService->queueAttendeeWebhook(
- eventType: WebhookEventType::ATTENDEE_UPDATED,
- attendeeId: $updatedAttendee->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new AttendeeEvent(
+ type: DomainEventType::ATTENDEE_UPDATED,
+ attendeeId: $updatedAttendee->getId(),
+ )
);
return $updatedAttendee;
diff --git a/backend/app/Services/Application/Handlers/Attendee/PartialEditAttendeeHandler.php b/backend/app/Services/Application/Handlers/Attendee/PartialEditAttendeeHandler.php
index 316899d88b..e724b88d3e 100644
--- a/backend/app/Services/Application/Handlers/Attendee/PartialEditAttendeeHandler.php
+++ b/backend/app/Services/Application/Handlers/Attendee/PartialEditAttendeeHandler.php
@@ -3,12 +3,13 @@
namespace HiEvents\Services\Application\Handlers\Attendee;
use HiEvents\DomainObjects\AttendeeDomainObject;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Status\AttendeeStatus;
use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface;
use HiEvents\Services\Application\Handlers\Attendee\DTO\PartialEditAttendeeDTO;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\AttendeeEvent;
use Illuminate\Database\DatabaseManager;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Throwable;
@@ -19,7 +20,7 @@ public function __construct(
private readonly AttendeeRepositoryInterface $attendeeRepository,
private readonly ProductQuantityUpdateService $productQuantityService,
private readonly DatabaseManager $databaseManager,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -52,9 +53,11 @@ private function updateAttendee(PartialEditAttendeeDTO $data): AttendeeDomainObj
}
if ($statusIsUpdated && $data->status === AttendeeStatus::CANCELLED->name) {
- $this->webhookDispatchService->queueAttendeeWebhook(
- eventType: WebhookEventType::ATTENDEE_CANCELLED,
- attendeeId: $attendee->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new AttendeeEvent(
+ type: DomainEventType::ATTENDEE_CANCELLED,
+ attendeeId: $attendee->getId(),
+ )
);
}
diff --git a/backend/app/Services/Application/Handlers/CheckInList/Public/CreateAttendeeCheckInPublicHandler.php b/backend/app/Services/Application/Handlers/CheckInList/Public/CreateAttendeeCheckInPublicHandler.php
index 11b6e12f20..74916036c2 100644
--- a/backend/app/Services/Application/Handlers/CheckInList/Public/CreateAttendeeCheckInPublicHandler.php
+++ b/backend/app/Services/Application/Handlers/CheckInList/Public/CreateAttendeeCheckInPublicHandler.php
@@ -3,12 +3,13 @@
namespace HiEvents\Services\Application\Handlers\CheckInList\Public;
use HiEvents\DomainObjects\AttendeeCheckInDomainObject;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\Exceptions\CannotCheckInException;
use HiEvents\Services\Application\Handlers\CheckInList\Public\DTO\CreateAttendeeCheckInPublicDTO;
use HiEvents\Services\Domain\CheckInList\CreateAttendeeCheckInService;
use HiEvents\Services\Domain\CheckInList\DTO\CreateAttendeeCheckInsResponseDTO;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\CheckinEvent;
use Psr\Log\LoggerInterface;
use Throwable;
@@ -17,7 +18,7 @@ class CreateAttendeeCheckInPublicHandler
public function __construct(
private readonly CreateAttendeeCheckInService $createAttendeeCheckInService,
private readonly LoggerInterface $logger,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -42,9 +43,11 @@ public function handle(CreateAttendeeCheckInPublicDTO $checkInData): CreateAtten
/** @var AttendeeCheckInDomainObject $checkIn */
foreach ($checkIns->attendeeCheckIns as $checkIn) {
- $this->webhookDispatchService->queueCheckInWebhook(
- WebhookEventType::CHECKIN_CREATED,
- $checkIn->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new CheckinEvent(
+ type: DomainEventType::CHECKIN_CREATED,
+ attendeeCheckinId: $checkIn->getId(),
+ )
);
}
diff --git a/backend/app/Services/Application/Handlers/CheckInList/Public/DeleteAttendeeCheckInPublicHandler.php b/backend/app/Services/Application/Handlers/CheckInList/Public/DeleteAttendeeCheckInPublicHandler.php
index 09efe08013..569b4c3428 100644
--- a/backend/app/Services/Application/Handlers/CheckInList/Public/DeleteAttendeeCheckInPublicHandler.php
+++ b/backend/app/Services/Application/Handlers/CheckInList/Public/DeleteAttendeeCheckInPublicHandler.php
@@ -2,42 +2,51 @@
namespace HiEvents\Services\Application\Handlers\CheckInList\Public;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\Exceptions\CannotCheckInException;
use HiEvents\Services\Application\Handlers\CheckInList\Public\DTO\DeleteAttendeeCheckInPublicDTO;
use HiEvents\Services\Domain\CheckInList\DeleteAttendeeCheckInService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\CheckinEvent;
+use Illuminate\Database\DatabaseManager;
use Psr\Log\LoggerInterface;
+use Throwable;
class DeleteAttendeeCheckInPublicHandler
{
public function __construct(
private readonly DeleteAttendeeCheckInService $deleteAttendeeCheckInService,
private readonly LoggerInterface $logger,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
+ private readonly DatabaseManager $databaseManager
)
{
}
/**
* @throws CannotCheckInException
+ * @throws Throwable
*/
public function handle(DeleteAttendeeCheckInPublicDTO $checkInData): void
{
- $deletedCheckInId = $this->deleteAttendeeCheckInService->deleteAttendeeCheckIn(
- $checkInData->checkInListShortId,
- $checkInData->checkInShortId,
- );
+ $this->databaseManager->transaction(function () use ($checkInData) {
+ $deletedCheckInId = $this->deleteAttendeeCheckInService->deleteAttendeeCheckIn(
+ $checkInData->checkInListShortId,
+ $checkInData->checkInShortId,
+ );
- $this->logger->info('Attendee check-in deleted', [
- 'check_in_list_uuid' => $checkInData->checkInListShortId,
- 'attendee_public_id' => $checkInData->checkInShortId,
- 'check_in_user_ip_address' => $checkInData->checkInUserIpAddress,
- ]);
+ $this->logger->info('Attendee check-in deleted', [
+ 'check_in_list_uuid' => $checkInData->checkInListShortId,
+ 'attendee_public_id' => $checkInData->checkInShortId,
+ 'check_in_user_ip_address' => $checkInData->checkInUserIpAddress,
+ ]);
- $this->webhookDispatchService->queueCheckInWebhook(
- eventType: WebhookEventType::CHECKIN_DELETED,
- attendeeCheckInId: $deletedCheckInId,
- );
+ $this->domainEventDispatcherService->dispatch(
+ new CheckinEvent(
+ type: DomainEventType::CHECKIN_DELETED,
+ attendeeCheckinId: $deletedCheckInId,
+ )
+ );
+ });
}
}
diff --git a/backend/app/Services/Application/Handlers/Order/CancelOrderHandler.php b/backend/app/Services/Application/Handlers/Order/CancelOrderHandler.php
index 0b30fb8ff5..c44af907eb 100644
--- a/backend/app/Services/Application/Handlers/Order/CancelOrderHandler.php
+++ b/backend/app/Services/Application/Handlers/Order/CancelOrderHandler.php
@@ -2,14 +2,12 @@
namespace HiEvents\Services\Application\Handlers\Order;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract;
use HiEvents\DomainObjects\OrderDomainObject;
use HiEvents\Exceptions\ResourceConflictException;
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Services\Application\Handlers\Order\DTO\CancelOrderDTO;
use HiEvents\Services\Domain\Order\OrderCancelService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Database\DatabaseManager;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Throwable;
diff --git a/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php b/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php
index b998d84d52..1604f253e5 100644
--- a/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php
+++ b/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php
@@ -8,7 +8,6 @@
use Exception;
use HiEvents\DomainObjects\AttendeeDomainObject;
use HiEvents\DomainObjects\Enums\ProductType;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\ProductPriceDomainObjectAbstract;
@@ -34,7 +33,9 @@
use HiEvents\Services\Application\Handlers\Order\DTO\OrderQuestionsDTO;
use HiEvents\Services\Domain\Payment\Stripe\EventHandlers\PaymentIntentSucceededHandler;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use RuntimeException;
@@ -51,7 +52,7 @@ public function __construct(
private readonly QuestionAnswerRepositoryInterface $questionAnswersRepository,
private readonly ProductQuantityUpdateService $productQuantityUpdateService,
private readonly ProductPriceRepositoryInterface $productPriceRepository,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -87,9 +88,11 @@ public function handle(string $orderShortId, CompleteOrderDTO $orderData): Order
OrderStatusChangedEvent::dispatch($updatedOrder);
if ($updatedOrder->isOrderCompleted()) {
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_CREATED,
- orderId: $updatedOrder->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(
+ type: DomainEventType::ORDER_CREATED,
+ orderId: $updatedOrder->getId(),
+ )
);
}
diff --git a/backend/app/Services/Application/Handlers/Order/TransitionOrderToOfflinePaymentHandler.php b/backend/app/Services/Application/Handlers/Order/TransitionOrderToOfflinePaymentHandler.php
index 1b41da015c..b7d5777347 100644
--- a/backend/app/Services/Application/Handlers/Order/TransitionOrderToOfflinePaymentHandler.php
+++ b/backend/app/Services/Application/Handlers/Order/TransitionOrderToOfflinePaymentHandler.php
@@ -3,7 +3,6 @@
namespace HiEvents\Services\Application\Handlers\Order;
use HiEvents\DomainObjects\Enums\PaymentProviders;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\EventSettingDomainObject;
use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract;
use HiEvents\DomainObjects\OrderDomainObject;
@@ -17,7 +16,9 @@
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Services\Application\Handlers\Order\DTO\TransitionOrderToOfflinePaymentPublicDTO;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Database\DatabaseManager;
class TransitionOrderToOfflinePaymentHandler
@@ -27,7 +28,7 @@ public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly DatabaseManager $databaseManager,
private readonly EventSettingsRepositoryInterface $eventSettingsRepository,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
@@ -62,9 +63,11 @@ public function handle(TransitionOrderToOfflinePaymentPublicDTO $dto): OrderDoma
createInvoice: $eventSettings->getEnableInvoicing(),
));
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_CREATED,
- orderId: $order->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(
+ type: DomainEventType::ORDER_CREATED,
+ orderId: $order->getId(),
+ ),
);
return $order;
diff --git a/backend/app/Services/Application/Handlers/Product/EditProductHandler.php b/backend/app/Services/Application/Handlers/Product/EditProductHandler.php
index 5044685eda..d45c9b710a 100644
--- a/backend/app/Services/Application/Handlers/Product/EditProductHandler.php
+++ b/backend/app/Services/Application/Handlers/Product/EditProductHandler.php
@@ -5,7 +5,6 @@
namespace HiEvents\Services\Application\Handlers\Product;
use Exception;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Interfaces\DomainObjectInterface;
use HiEvents\DomainObjects\ProductDomainObject;
use HiEvents\DomainObjects\ProductPriceDomainObject;
@@ -14,13 +13,14 @@
use HiEvents\Repository\Interfaces\EventRepositoryInterface;
use HiEvents\Repository\Interfaces\ProductRepositoryInterface;
use HiEvents\Services\Application\Handlers\Product\DTO\UpsertProductDTO;
-use HiEvents\Services\Domain\Product\ProductOrderingService;
use HiEvents\Services\Domain\Product\ProductPriceUpdateService;
use HiEvents\Services\Domain\ProductCategory\GetProductCategoryService;
use HiEvents\Services\Domain\Tax\DTO\TaxAndProductAssociateParams;
use HiEvents\Services\Domain\Tax\TaxAndProductAssociationService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\ProductEvent;
use HiEvents\Services\Infrastructure\HtmlPurifier\HtmlPurifierService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Database\DatabaseManager;
use Throwable;
@@ -36,9 +36,8 @@ public function __construct(
private readonly ProductPriceUpdateService $priceUpdateService,
private readonly HtmlPurifierService $purifier,
private readonly EventRepositoryInterface $eventRepository,
- private readonly ProductOrderingService $productOrderingService,
private readonly GetProductCategoryService $getProductCategoryService,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -65,9 +64,11 @@ public function handle(UpsertProductDTO $productsData): DomainObjectInterface
$this->eventRepository->findById($productsData->event_id)
);
- $this->webhookDispatchService->queueProductWebhook(
- eventType: WebhookEventType::PRODUCT_UPDATED,
- productId: $product->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new ProductEvent(
+ type: DomainEventType::PRODUCT_UPDATED,
+ productId: $product->getId(),
+ )
);
return $this->productRepository
diff --git a/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php b/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php
index 03e997aac3..f1937a52f3 100644
--- a/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php
+++ b/backend/app/Services/Domain/Mail/SendEventEmailMessagesService.php
@@ -11,6 +11,7 @@
use HiEvents\DomainObjects\Status\AttendeeStatus;
use HiEvents\DomainObjects\Status\MessageStatus;
use HiEvents\Exceptions\UnableToSendMessageException;
+use HiEvents\Jobs\Event\SendEventEmailJob;
use HiEvents\Mail\Event\EventMessage;
use HiEvents\Repository\Eloquent\Value\Relationship;
use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface;
@@ -19,20 +20,22 @@
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Repository\Interfaces\UserRepositoryInterface;
use HiEvents\Services\Application\Handlers\Message\DTO\SendMessageDTO;
-use Illuminate\Mail\Mailer;
+use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Support\Collection;
use Symfony\Component\HttpKernel\Log\Logger;
class SendEventEmailMessagesService
{
+ private array $sentEmails = [];
+
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly AttendeeRepositoryInterface $attendeeRepository,
private readonly EventRepositoryInterface $eventRepository,
private readonly MessageRepositoryInterface $messageRepository,
private readonly UserRepositoryInterface $userRepository,
- private readonly Mailer $mailer,
- private readonly Logger $logger
+ private readonly Logger $logger,
+ private readonly Dispatcher $dispatcher,
)
{
}
@@ -202,24 +205,6 @@ private function sendEmailToMessageSender(SendMessageDTO $messageData, EventDoma
);
}
- private function sendMessage(
- string $emailAddress,
- string $fullName,
- SendMessageDTO $messageData,
- EventDomainObject $event,
- ): void
- {
- $this->mailer->to(
- $emailAddress,
- $fullName
- )
- ->queue(new EventMessage(
- event: $event,
- eventSettings: $event->getEventSettings(),
- messageData: $messageData
- ));
- }
-
private function sendProductMessages(SendMessageDTO $messageData, EventDomainObject $event): void
{
$orders = $this->orderRepository->findOrdersAssociatedWithProducts(
@@ -243,4 +228,31 @@ private function sendProductMessages(SendMessageDTO $messageData, EventDomainObj
);
});
}
+
+ private function sendMessage(
+ string $emailAddress,
+ string $fullName,
+ SendMessageDTO $messageData,
+ EventDomainObject $event,
+ ): void
+ {
+ if (in_array($emailAddress, $this->sentEmails, true)) {
+ return;
+ }
+
+ $this->dispatcher->dispatch(
+ new SendEventEmailJob(
+ email: $emailAddress,
+ toName: $fullName,
+ eventMessage: new EventMessage(
+ event: $event,
+ eventSettings: $event->getEventSettings(),
+ messageData: $messageData
+ ),
+ messageData: $messageData,
+ )
+ );
+
+ $this->sentEmails[] = $emailAddress;
+ }
}
diff --git a/backend/app/Services/Domain/Order/EditOrderService.php b/backend/app/Services/Domain/Order/EditOrderService.php
index 5bfb41843e..186b73dba7 100644
--- a/backend/app/Services/Domain/Order/EditOrderService.php
+++ b/backend/app/Services/Domain/Order/EditOrderService.php
@@ -2,19 +2,20 @@
namespace HiEvents\Services\Domain\Order;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\OrderDomainObject;
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Database\DatabaseManager;
use Throwable;
class EditOrderService
{
public function __construct(
- private readonly OrderRepositoryInterface $orderRepository,
- private readonly WebhookDispatchService $webhookDispatchService,
- private readonly DatabaseManager $databaseManager,
+ private readonly OrderRepositoryInterface $orderRepository,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
+ private readonly DatabaseManager $databaseManager,
)
{
}
@@ -43,9 +44,11 @@ public function editOrder(
]
);
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_UPDATED,
- orderId: $id,
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(
+ type: DomainEventType::ORDER_UPDATED,
+ orderId: $id,
+ ),
);
return $this->orderRepository->findById($id);
diff --git a/backend/app/Services/Domain/Order/MarkOrderAsPaidService.php b/backend/app/Services/Domain/Order/MarkOrderAsPaidService.php
index ea5a6bf906..4682d3f2bc 100644
--- a/backend/app/Services/Domain/Order/MarkOrderAsPaidService.php
+++ b/backend/app/Services/Domain/Order/MarkOrderAsPaidService.php
@@ -6,7 +6,6 @@
use HiEvents\DomainObjects\AccountDomainObject;
use HiEvents\DomainObjects\AttendeeDomainObject;
use HiEvents\DomainObjects\Enums\PaymentProviders;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\DomainObjects\EventSettingDomainObject;
use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract;
@@ -27,7 +26,9 @@
use HiEvents\Repository\Interfaces\InvoiceRepositoryInterface;
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Services\Domain\Mail\SendOrderDetailsService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Database\DatabaseManager;
use Throwable;
@@ -38,7 +39,7 @@ public function __construct(
private readonly DatabaseManager $databaseManager,
private readonly InvoiceRepositoryInterface $invoiceRepository,
private readonly AttendeeRepositoryInterface $attendeeRepository,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
private readonly OrderApplicationFeeCalculationService $orderApplicationFeeCalculationService,
private readonly EventRepositoryInterface $eventRepository,
private readonly OrderApplicationFeeService $orderApplicationFeeService,
@@ -88,9 +89,11 @@ public function markOrderAsPaid(
sendEmails: false
));
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_MARKED_AS_PAID,
- orderId: $orderId,
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(
+ type: DomainEventType::ORDER_MARKED_AS_PAID,
+ orderId: $orderId,
+ ),
);
$this->storeApplicationFeePayment($updatedOrder);
diff --git a/backend/app/Services/Domain/Order/OrderCancelService.php b/backend/app/Services/Domain/Order/OrderCancelService.php
index 31ffbc3058..2c02a40ddf 100644
--- a/backend/app/Services/Domain/Order/OrderCancelService.php
+++ b/backend/app/Services/Domain/Order/OrderCancelService.php
@@ -3,7 +3,6 @@
namespace HiEvents\Services\Domain\Order;
use HiEvents\DomainObjects\AttendeeDomainObject;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\EventSettingDomainObject;
use HiEvents\DomainObjects\OrderDomainObject;
use HiEvents\DomainObjects\Status\AttendeeStatus;
@@ -13,7 +12,9 @@
use HiEvents\Repository\Interfaces\EventRepositoryInterface;
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Database\DatabaseManager;
use Throwable;
@@ -27,7 +28,7 @@ public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly DatabaseManager $databaseManager,
private readonly ProductQuantityUpdateService $productQuantityService,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -55,9 +56,11 @@ public function cancelOrder(OrderDomainObject $order): void
eventSettings: $event->getEventSettings(),
));
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_CANCELLED,
- orderId: $order->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(
+ type: DomainEventType::ORDER_CANCELLED,
+ orderId: $order->getId(),
+ ),
);
});
}
diff --git a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/ChargeRefundUpdatedHandler.php b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/ChargeRefundUpdatedHandler.php
index 5ba5122e69..3aea1ba796 100644
--- a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/ChargeRefundUpdatedHandler.php
+++ b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/ChargeRefundUpdatedHandler.php
@@ -4,7 +4,6 @@
use Brick\Money\Money;
use HiEvents\DomainObjects\Enums\PaymentProviders;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract;
use HiEvents\DomainObjects\OrderDomainObject;
use HiEvents\DomainObjects\Status\OrderRefundStatus;
@@ -12,7 +11,9 @@
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Repository\Interfaces\StripePaymentsRepositoryInterface;
use HiEvents\Services\Domain\EventStatistics\EventStatisticsUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use HiEvents\Values\MoneyValue;
use Illuminate\Database\DatabaseManager;
use Illuminate\Log\Logger;
@@ -28,7 +29,7 @@ public function __construct(
private readonly DatabaseManager $databaseManager,
private readonly EventStatisticsUpdateService $eventStatisticsUpdateService,
private readonly OrderRefundRepositoryInterface $orderRefundRepository,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -82,9 +83,11 @@ public function handleEvent(Refund $refund): void
'refund_id' => $refund->id,
]);
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_REFUNDED,
- orderId: $order->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(
+ type: DomainEventType::ORDER_REFUNDED,
+ orderId: $order->getId()
+ ),
);
});
}
@@ -102,9 +105,9 @@ private function updateEventStatistics(OrderDomainObject $order, MoneyValue $amo
private function updateOrderRefundedAmount(int $orderId, float $refundedAmount): void
{
$this->orderRepository->increment(
- $orderId,
- OrderDomainObjectAbstract::TOTAL_REFUNDED,
- $refundedAmount
+ id: $orderId,
+ column: OrderDomainObjectAbstract::TOTAL_REFUNDED,
+ amount: $refundedAmount
);
}
diff --git a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php
index 9a403f0fd1..1da220c18e 100644
--- a/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php
+++ b/backend/app/Services/Domain/Payment/Stripe/EventHandlers/PaymentIntentSucceededHandler.php
@@ -8,7 +8,6 @@
use Brick\Money\Exception\UnknownCurrencyException;
use Carbon\Carbon;
use HiEvents\DomainObjects\Enums\PaymentProviders;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\StripePaymentDomainObjectAbstract;
use HiEvents\DomainObjects\OrderDomainObject;
@@ -26,7 +25,9 @@
use HiEvents\Services\Domain\Order\OrderApplicationFeeService;
use HiEvents\Services\Domain\Payment\Stripe\StripeRefundExpiredOrderService;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Cache\Repository;
use Illuminate\Database\DatabaseManager;
use Psr\Log\LoggerInterface;
@@ -45,7 +46,7 @@ public function __construct(
private readonly DatabaseManager $databaseManager,
private readonly LoggerInterface $logger,
private readonly Repository $cache,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
private readonly OrderApplicationFeeService $orderApplicationFeeService,
)
{
@@ -92,9 +93,11 @@ public function handleEvent(PaymentIntent $paymentIntent): void
OrderStatusChangedEvent::dispatch($updatedOrder);
- $this->webhookDispatchService->queueOrderWebhook(
- eventType: WebhookEventType::ORDER_CREATED,
- orderId: $updatedOrder->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new OrderEvent(
+ type: DomainEventType::ORDER_CREATED,
+ orderId: $updatedOrder->getId()
+ ),
);
$this->markPaymentIntentAsHandled($paymentIntent, $updatedOrder);
diff --git a/backend/app/Services/Domain/Product/CreateProductService.php b/backend/app/Services/Domain/Product/CreateProductService.php
index 6a79f73ea5..2f5179613f 100644
--- a/backend/app/Services/Domain/Product/CreateProductService.php
+++ b/backend/app/Services/Domain/Product/CreateProductService.php
@@ -3,15 +3,16 @@
namespace HiEvents\Services\Domain\Product;
use Exception;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\ProductDomainObject;
use HiEvents\Helper\DateHelper;
use HiEvents\Repository\Interfaces\EventRepositoryInterface;
use HiEvents\Repository\Interfaces\ProductRepositoryInterface;
use HiEvents\Services\Domain\Tax\DTO\TaxAndProductAssociateParams;
use HiEvents\Services\Domain\Tax\TaxAndProductAssociationService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\ProductEvent;
use HiEvents\Services\Infrastructure\HtmlPurifier\HtmlPurifierService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Collection;
use Throwable;
@@ -26,7 +27,7 @@ public function __construct(
private readonly HtmlPurifierService $purifier,
private readonly EventRepositoryInterface $eventRepository,
private readonly ProductOrderingService $productOrderingService,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -49,9 +50,11 @@ public function createProduct(
$product = $this->createProductPrices($persistedProduct, $product);
- $this->webhookDispatchService->queueProductWebhook(
- eventType: WebhookEventType::PRODUCT_CREATED,
- productId: $product->getId(),
+ $this->domainEventDispatcherService->dispatch(
+ new ProductEvent(
+ type: DomainEventType::PRODUCT_CREATED,
+ productId: $product->getId(),
+ )
);
return $product;
diff --git a/backend/app/Services/Domain/Product/DeleteProductService.php b/backend/app/Services/Domain/Product/DeleteProductService.php
index ffdd285a4a..091b941180 100644
--- a/backend/app/Services/Domain/Product/DeleteProductService.php
+++ b/backend/app/Services/Domain/Product/DeleteProductService.php
@@ -2,13 +2,14 @@
namespace HiEvents\Services\Domain\Product;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\ProductPriceDomainObjectAbstract;
use HiEvents\Exceptions\CannotDeleteEntityException;
use HiEvents\Repository\Interfaces\ProductPriceRepositoryInterface;
use HiEvents\Repository\Interfaces\ProductRepositoryInterface;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\ProductEvent;
use Illuminate\Database\DatabaseManager;
use Psr\Log\LoggerInterface;
use Throwable;
@@ -20,7 +21,7 @@ public function __construct(
private readonly ProductPriceRepositoryInterface $productPriceRepository,
private readonly LoggerInterface $logger,
private readonly DatabaseManager $databaseManager,
- private readonly WebhookDispatchService $webhookDispatchService,
+ private readonly DomainEventDispatcherService $domainEventDispatcherService,
)
{
}
@@ -52,9 +53,11 @@ public function deleteProduct(int $productId, int $eventId): void
);
});
- $this->webhookDispatchService->queueProductWebhook(
- eventType: WebhookEventType::PRODUCT_DELETED,
- productId: $productId,
+ $this->domainEventDispatcherService->dispatch(
+ new ProductEvent(
+ type: DomainEventType::PRODUCT_DELETED,
+ productId: $productId,
+ )
);
$this->logger->info(
diff --git a/backend/app/Services/Infrastructure/DomainEvents/DomainEventDispatcherService.php b/backend/app/Services/Infrastructure/DomainEvents/DomainEventDispatcherService.php
new file mode 100644
index 0000000000..5c833d861f
--- /dev/null
+++ b/backend/app/Services/Infrastructure/DomainEvents/DomainEventDispatcherService.php
@@ -0,0 +1,18 @@
+dispatcher->dispatch($event);
+ }
+}
diff --git a/backend/app/DomainObjects/Enums/WebhookEventType.php b/backend/app/Services/Infrastructure/DomainEvents/Enums/DomainEventType.php
similarity index 82%
rename from backend/app/DomainObjects/Enums/WebhookEventType.php
rename to backend/app/Services/Infrastructure/DomainEvents/Enums/DomainEventType.php
index 2526d3562b..dbaf38aecd 100644
--- a/backend/app/DomainObjects/Enums/WebhookEventType.php
+++ b/backend/app/Services/Infrastructure/DomainEvents/Enums/DomainEventType.php
@@ -1,8 +1,10 @@
onQueue($this->config->get('queue.webhook_queue_name'));
- }
-
- public function queueProductWebhook(WebhookEventType $eventType, int $productId): void
- {
- DispatchProductWebhookJob::dispatch(
- productId: $productId,
- eventType: $eventType,
- )->onQueue($this->config->get('queue.webhook_queue_name'));
- }
-
- public function queueCheckInWebhook(WebhookEventType $eventType, int $attendeeCheckInId): void
- {
- DispatchCheckInWebhookJob::dispatch(
- attendeeCheckInId: $attendeeCheckInId,
- eventType: $eventType,
- )->onQueue($this->config->get('queue.webhook_queue_name'));
- }
-
- public function queueAttendeeWebhook(WebhookEventType $eventType, int $attendeeId): void
- {
- DispatchAttendeeWebhookJob::dispatch(
- attendeeId: $attendeeId,
- eventType: $eventType,
- )->onQueue($this->config->get('queue.webhook_queue_name'));
- }
-
- public function dispatchAttendeeWebhook(WebhookEventType $eventType, int $attendeeId): void
+ public function dispatchAttendeeWebhook(DomainEventType $eventType, int $attendeeId): void
{
$attendee = $this->attendeeRepository
->loadRelation(new Relationship(
@@ -92,7 +54,7 @@ public function dispatchAttendeeWebhook(WebhookEventType $eventType, int $attend
);
}
- public function dispatchCheckInWebhook(WebhookEventType $eventType, int $attendeeCheckInId): void
+ public function dispatchCheckInWebhook(DomainEventType $eventType, int $attendeeCheckInId): void
{
$attendeeCheckIn = $this->attendeeCheckInRepository
->loadRelation(new Relationship(
@@ -109,7 +71,7 @@ public function dispatchCheckInWebhook(WebhookEventType $eventType, int $attende
);
}
- public function dispatchProductWebhook(WebhookEventType $eventType, int $productId): void
+ public function dispatchProductWebhook(DomainEventType $eventType, int $productId): void
{
$product = $this->productRepository
->loadRelation(ProductPriceDomainObject::class)
@@ -124,7 +86,7 @@ public function dispatchProductWebhook(WebhookEventType $eventType, int $product
);
}
- public function dispatchOrderWebhook(WebhookEventType $eventType, int $orderId): void
+ public function dispatchOrderWebhook(DomainEventType $eventType, int $orderId): void
{
$order = $this->orderRepository
->loadRelation(OrderItemDomainObject::class)
@@ -141,21 +103,21 @@ public function dispatchOrderWebhook(WebhookEventType $eventType, int $orderId):
->loadRelation(QuestionAndAnswerViewDomainObject::class)
->findById($orderId);
- if ($eventType === WebhookEventType::ORDER_CREATED) {
+ if ($eventType === DomainEventType::ORDER_CREATED) {
/** @var AttendeeDomainObject $attendee */
foreach ($order->getAttendees() as $attendee) {
- $this->queueAttendeeWebhook(
- eventType: WebhookEventType::ATTENDEE_CREATED,
+ $this->dispatchAttendeeWebhook(
+ eventType: DomainEventType::ATTENDEE_CREATED,
attendeeId: $attendee->getId(),
);
}
}
- if ($eventType === WebhookEventType::ORDER_CANCELLED) {
+ if ($eventType === DomainEventType::ORDER_CANCELLED) {
/** @var AttendeeDomainObject $attendee */
foreach ($order->getAttendees() as $attendee) {
- $this->queueAttendeeWebhook(
- eventType: WebhookEventType::ATTENDEE_CANCELLED,
+ $this->dispatchAttendeeWebhook(
+ eventType: DomainEventType::ATTENDEE_CANCELLED,
attendeeId: $attendee->getId(),
);
}
@@ -168,7 +130,7 @@ public function dispatchOrderWebhook(WebhookEventType $eventType, int $orderId):
);
}
- private function dispatchWebhook(WebhookEventType $eventType, JsonResource $payload, int $eventId): void
+ private function dispatchWebhook(DomainEventType $eventType, JsonResource $payload, int $eventId): void
{
/** @var Collection $webhooks */
$webhooks = $this->webhookRepository->findWhere([
diff --git a/backend/config/mail.php b/backend/config/mail.php
index 3b6cca043d..811c09f308 100644
--- a/backend/config/mail.php
+++ b/backend/config/mail.php
@@ -1,6 +1,7 @@
env('MAIL_RATE_LIMIT_PER_SECOND', 14),
/*
|--------------------------------------------------------------------------
diff --git a/backend/config/queue.php b/backend/config/queue.php
index c4260ae98f..52622994f0 100644
--- a/backend/config/queue.php
+++ b/backend/config/queue.php
@@ -65,7 +65,7 @@
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
- 'after_commit' => false,
+ 'after_commit' => true,
],
'redis' => [
diff --git a/backend/database/migrations/2025_03_24_052900_add_outgoing_messages_table.php b/backend/database/migrations/2025_03_24_052900_add_outgoing_messages_table.php
new file mode 100644
index 0000000000..0a3afe5685
--- /dev/null
+++ b/backend/database/migrations/2025_03_24_052900_add_outgoing_messages_table.php
@@ -0,0 +1,37 @@
+id();
+
+ $table->foreignId('event_id')->constrained()->cascadeOnDelete();
+ $table->foreignId('message_id')->constrained()->cascadeOnDelete();
+ $table->string('subject');
+ $table->string('recipient');
+ $table->string('status');
+
+ $table->index('event_id');
+ $table->index('message_id');
+
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('outgoing_messages');
+ }
+};
diff --git a/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php b/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php
index d65f42255f..9d003d89e6 100644
--- a/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php
+++ b/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php
@@ -5,7 +5,6 @@
use Carbon\Carbon;
use Exception;
use HiEvents\DomainObjects\AttendeeDomainObject;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\OrderDomainObject;
use HiEvents\DomainObjects\OrderItemDomainObject;
use HiEvents\DomainObjects\ProductPriceDomainObject;
@@ -20,7 +19,10 @@
use HiEvents\Services\Application\Handlers\Order\DTO\CompleteOrderOrderDTO;
use HiEvents\Services\Application\Handlers\Order\DTO\CompleteOrderProductDataDTO;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
-use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\BaseDomainEvent;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use Illuminate\Database\Connection;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Bus;
@@ -40,7 +42,7 @@ class CompleteOrderHandlerTest extends TestCase
private ProductQuantityUpdateService|MockInterface $productQuantityUpdateService;
private ProductPriceRepositoryInterface|MockInterface $productPriceRepository;
private CompleteOrderHandler $completeOrderHandler;
- private WebhookDispatchService $webhookDispatchService;
+ private DomainEventDispatcherService $domainEventDispatcherService;
protected function setUp(): void
{
@@ -55,7 +57,7 @@ protected function setUp(): void
$this->questionAnswersRepository = Mockery::mock(QuestionAnswerRepositoryInterface::class);
$this->productQuantityUpdateService = Mockery::mock(ProductQuantityUpdateService::class);
$this->productPriceRepository = Mockery::mock(ProductPriceRepositoryInterface::class);
- $this->webhookDispatchService = Mockery::mock(WebhookDispatchService::class);
+ $this->domainEventDispatcherService = Mockery::mock(DomainEventDispatcherService::class);
$this->completeOrderHandler = new CompleteOrderHandler(
$this->orderRepository,
@@ -63,7 +65,7 @@ protected function setUp(): void
$this->questionAnswersRepository,
$this->productQuantityUpdateService,
$this->productPriceRepository,
- $this->webhookDispatchService
+ $this->domainEventDispatcherService
);
}
@@ -164,8 +166,11 @@ public function testHandleUpdatesProductQuantitiesForFreeOrder(): void
$this->productQuantityUpdateService->shouldReceive('updateQuantitiesFromOrder')->once();
- $this->webhookDispatchService->shouldReceive('queueOrderWebhook')
- ->with(WebhookEventType::ORDER_CREATED, $updatedOrder->getId())
+ $this->domainEventDispatcherService->shouldReceive('dispatch')
+ ->withArgs(function (OrderEvent $event) use ($order) {
+ return $event->type === DomainEventType::ORDER_CREATED
+ && $event->orderId === $order->getId();
+ })
->once();
$order = $this->completeOrderHandler->handle($orderShortId, $orderData);
@@ -252,10 +257,10 @@ private function createMockCompleteOrderDTO(): CompleteOrderDTO
);
$attendeeDTO = new CompleteOrderProductDataDTO(
+ product_price_id: 1,
first_name: 'John',
last_name: 'Doe',
- email: 'john@example.com',
- product_price_id: 1
+ email: 'john@example.com'
);
return new CompleteOrderDTO(
diff --git a/backend/tests/Unit/Services/Domain/Order/OrderCancelServiceTest.php b/backend/tests/Unit/Services/Domain/Order/OrderCancelServiceTest.php
index 4f1f93bbba..d40e0313af 100644
--- a/backend/tests/Unit/Services/Domain/Order/OrderCancelServiceTest.php
+++ b/backend/tests/Unit/Services/Domain/Order/OrderCancelServiceTest.php
@@ -3,7 +3,6 @@
namespace Tests\Unit\Services\Domain\Order;
use HiEvents\DomainObjects\AttendeeDomainObject;
-use HiEvents\DomainObjects\Enums\WebhookEventType;
use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\DomainObjects\EventSettingDomainObject;
use HiEvents\DomainObjects\OrderDomainObject;
@@ -14,6 +13,9 @@
use HiEvents\Repository\Interfaces\OrderRepositoryInterface;
use HiEvents\Services\Domain\Order\OrderCancelService;
use HiEvents\Services\Domain\Product\ProductQuantityUpdateService;
+use HiEvents\Services\Infrastructure\DomainEvents\DomainEventDispatcherService;
+use HiEvents\Services\Infrastructure\DomainEvents\Enums\DomainEventType;
+use HiEvents\Services\Infrastructure\DomainEvents\Events\OrderEvent;
use HiEvents\Services\Infrastructure\Webhook\WebhookDispatchService;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Database\DatabaseManager;
@@ -31,7 +33,7 @@ class OrderCancelServiceTest extends TestCase
private DatabaseManager $databaseManager;
private ProductQuantityUpdateService $productQuantityService;
private OrderCancelService $service;
- private WebhookDispatchService $webhookDispatchService;
+ private DomainEventDispatcherService $domainEventDispatcherService;
protected function setUp(): void
{
@@ -43,7 +45,7 @@ protected function setUp(): void
$this->orderRepository = m::mock(OrderRepositoryInterface::class);
$this->databaseManager = m::mock(DatabaseManager::class);
$this->productQuantityService = m::mock(ProductQuantityUpdateService::class);
- $this->webhookDispatchService = m::mock(WebhookDispatchService::class);
+ $this->domainEventDispatcherService = m::mock(DomainEventDispatcherService::class);
$this->service = new OrderCancelService(
mailer: $this->mailer,
@@ -52,7 +54,7 @@ protected function setUp(): void
orderRepository: $this->orderRepository,
databaseManager: $this->databaseManager,
productQuantityService: $this->productQuantityService,
- webhookDispatchService: $this->webhookDispatchService,
+ domainEventDispatcherService: $this->domainEventDispatcherService,
);
}
@@ -105,8 +107,11 @@ public function testCancelOrder(): void
return $mail instanceof OrderCancelled;
});
- $this->webhookDispatchService->shouldReceive('queueOrderWebhook')
- ->with(WebhookEventType::ORDER_CANCELLED, 1)
+ $this->domainEventDispatcherService->shouldReceive('dispatch')
+ ->withArgs(function (OrderEvent $event) use ($order) {
+ return $event->type === DomainEventType::ORDER_CANCELLED
+ && $event->orderId === $order->getId();
+ })
->once();
$this->databaseManager->shouldReceive('transaction')->once()->andReturnUsing(function ($callback) {
diff --git a/frontend/src/components/common/CustomSelect/index.tsx b/frontend/src/components/common/CustomSelect/index.tsx
index d4fb4d619c..944c4a81b8 100644
--- a/frontend/src/components/common/CustomSelect/index.tsx
+++ b/frontend/src/components/common/CustomSelect/index.tsx
@@ -112,7 +112,9 @@ export const CustomSelect = ({
};
return (
-
+
{label && (
{label}
diff --git a/frontend/src/components/modals/WebhookLogsModal/index.tsx b/frontend/src/components/modals/WebhookLogsModal/index.tsx
index d43b9c6b0f..979a78f12b 100644
--- a/frontend/src/components/modals/WebhookLogsModal/index.tsx
+++ b/frontend/src/components/modals/WebhookLogsModal/index.tsx
@@ -183,11 +183,11 @@ export const WebhookLogsModal = ({onClose, webhookId}: WebhookLogsModalProps) =>
)}
{logs && logs.length > 0 && (
-
+ <>
{logs.map((log) => (
))}
-
+ >
)}
);
diff --git a/frontend/src/components/routes/product-widget/SelectProducts/index.tsx b/frontend/src/components/routes/product-widget/SelectProducts/index.tsx
index 44d3c32654..fee491fd96 100644
--- a/frontend/src/components/routes/product-widget/SelectProducts/index.tsx
+++ b/frontend/src/components/routes/product-widget/SelectProducts/index.tsx
@@ -283,7 +283,7 @@ const SelectProducts = (props: SelectProductsProps) => {
If a new tab did not open, please {' '}
-
{t`click here`}.