mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-19 23:24:46 +02:00
Implement Webhooks (#548)
* feat: First Webhook PoC draft * feat: Dispatch Webhooks PoC * fix: typo in webhook configuration scope * Update 2024_04_21_162552_create_webhooks_table.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update 2024_04_21_162552_create_webhooks_table.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update 2024_04_21_162544_create_webhook_configurations_table.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update 2024_04_21_162544_create_webhook_configurations_table.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhooks.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhooksJob.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhookForConfiguration.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhookForConfiguration.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhookForConfiguration.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhooksJob.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhooksJob.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * Update DispatchWebhooksJob.php Co-authored-by: Lance Pioch <lancepioch@gmail.com> * chore: Implement Webhook Event Discovery * we got a test working for webhooks * WIP * Something is working! * More tests * clean up the tests now that they are passing * WIP * Don't use model specific events * WIP * WIP * WIP * WIP * WIP * Do it sync * Reset these * Don't need restored event type * Deleted some unused jobs * Find custom Events * Remove observers * Add custom event test * Run Pint * Add caching * Don't cache every single event * Fix tests * Run Pint * Phpstan fixes * Pint fix * Test fixes * Middleware unit test fix * Pint fixes * Remove index not working for older dbs * Use facade instead --------- Co-authored-by: Pascale Beier <mail@pascalebeier.de> Co-authored-by: Lance Pioch <lancepioch@gmail.com> Co-authored-by: Vehikl <go@vehikl.com>
This commit is contained in:
parent
5f77deb1fd
commit
86c369d7ce
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Saved extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Saving extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Updated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Updating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Subuser;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Creating extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
}
|
||||
}
|
71
app/Filament/Resources/WebhookResource.php
Normal file
71
app/Filament/Resources/WebhookResource.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\WebhookResource\Pages;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class WebhookResource extends Resource
|
||||
{
|
||||
protected static ?string $model = WebhookConfiguration::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-webhook';
|
||||
|
||||
protected static ?string $label = 'Webhooks';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('endpoint')->activeUrl()->required(),
|
||||
Forms\Components\TextInput::make('description')->nullable(),
|
||||
Forms\Components\CheckboxList::make('events')->lazy()->options(
|
||||
fn () => WebhookConfiguration::filamentCheckboxList()
|
||||
)
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->gridDirection('row')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
//
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListWebhookConfigurations::route('/'),
|
||||
'create' => Pages\CreateWebhookConfiguration::route('/create'),
|
||||
'edit' => Pages\EditWebhookConfiguration::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateWebhookConfiguration extends CreateRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditWebhookConfiguration extends EditRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListWebhookConfigurations extends ListRecords
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Api\Client\Servers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Notifications\RemovedFromServer;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@ -144,6 +145,11 @@ class SubuserController extends ClientApiController
|
||||
$log->transaction(function ($instance) use ($server, $subuser) {
|
||||
$subuser->delete();
|
||||
|
||||
$subuser->user->notify(new RemovedFromServer([
|
||||
'user' => $subuser->user->name_first,
|
||||
'name' => $subuser->server->name,
|
||||
]));
|
||||
|
||||
try {
|
||||
$this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id);
|
||||
} catch (DaemonConnectionException $exception) {
|
||||
|
40
app/Jobs/ProcessWebhook.php
Normal file
40
app/Jobs/ProcessWebhook.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ProcessWebhook implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
private WebhookConfiguration $webhookConfiguration,
|
||||
private string $eventName,
|
||||
private array $data
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
Http::post($this->webhookConfiguration->endpoint, $this->data)->throw();
|
||||
$successful = now();
|
||||
} catch (\Exception) {
|
||||
$successful = null;
|
||||
}
|
||||
|
||||
$this->webhookConfiguration->webhooks()->create([
|
||||
'payload' => $this->data,
|
||||
'successful_at' => $successful,
|
||||
'event' => $this->eventName,
|
||||
'endpoint' => $this->webhookConfiguration->endpoint,
|
||||
]);
|
||||
}
|
||||
}
|
39
app/Listeners/DispatchWebhooks.php
Normal file
39
app/Listeners/DispatchWebhooks.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use App\Models\WebhookConfiguration;
|
||||
|
||||
class DispatchWebhooks
|
||||
{
|
||||
public function handle(string $eventName, array $data): void
|
||||
{
|
||||
if (!$this->eventIsWatched($eventName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$matchingHooks = cache()->rememberForever("webhooks.$eventName", function () use ($eventName) {
|
||||
return WebhookConfiguration::query()->whereJsonContains('events', $eventName)->get();
|
||||
});
|
||||
|
||||
foreach ($matchingHooks ?? [] as $webhookConfig) {
|
||||
if (in_array($eventName, $webhookConfig->events)) {
|
||||
ProcessWebhook::dispatch($webhookConfig, $eventName, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function eventIsWatched(string $eventName): bool
|
||||
{
|
||||
$watchedEvents = cache()->rememberForever('watchedWebhooks', function () {
|
||||
return WebhookConfiguration::pluck('events')
|
||||
->flatten()
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
});
|
||||
|
||||
return in_array($eventName, $watchedEvents);
|
||||
}
|
||||
}
|
21
app/Models/Webhook.php
Normal file
21
app/Models/Webhook.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Webhook extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['payload', 'successful_at', 'event', 'endpoint'];
|
||||
|
||||
public function casts()
|
||||
{
|
||||
return [
|
||||
'payload' => 'array',
|
||||
'successful_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
}
|
120
app/Models/WebhookConfiguration.php
Normal file
120
app/Models/WebhookConfiguration.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class WebhookConfiguration extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'endpoint',
|
||||
'description',
|
||||
'events',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'events' => 'json',
|
||||
];
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
self::saved(static function (self $webhookConfiguration): void {
|
||||
$changedEvents = collect([
|
||||
...((array) $webhookConfiguration->events),
|
||||
...$webhookConfiguration->getOriginal('events', '[]'),
|
||||
])->unique();
|
||||
|
||||
$changedEvents->each(function (string $event) {
|
||||
cache()->forever("webhooks.$event", WebhookConfiguration::query()->whereJsonContains('events', $event)->get());
|
||||
});
|
||||
|
||||
cache()->forever('watchedWebhooks', WebhookConfiguration::pluck('events')->flatten()->unique()->values()->all());
|
||||
});
|
||||
}
|
||||
|
||||
public function webhooks(): HasMany
|
||||
{
|
||||
return $this->hasMany(Webhook::class);
|
||||
}
|
||||
|
||||
public static function allPossibleEvents(): array
|
||||
{
|
||||
return static::discoverCustomEvents() + static::allModelEvents();
|
||||
}
|
||||
|
||||
public static function filamentCheckboxList(): array
|
||||
{
|
||||
$list = [];
|
||||
$events = static::allPossibleEvents();
|
||||
foreach ($events as $event) {
|
||||
$list[$event] = static::transformClassName($event);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public static function transformClassName(string $event): string
|
||||
{
|
||||
return str($event)
|
||||
->after('eloquent.')
|
||||
->replace('App\\Models\\', '')
|
||||
->replace('App\\Events\\', 'event: ')
|
||||
->toString();
|
||||
}
|
||||
|
||||
public static function allModelEvents(): array
|
||||
{
|
||||
$eventTypes = ['created', 'updated', 'deleted'];
|
||||
$models = static::discoverModels();
|
||||
|
||||
$events = [];
|
||||
foreach ($models as $model) {
|
||||
foreach ($eventTypes as $eventType) {
|
||||
$events[] = "eloquent.$eventType: $model";
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
public static function discoverModels(): array
|
||||
{
|
||||
$namespace = 'App\\Models\\';
|
||||
$directory = app_path('Models');
|
||||
|
||||
$models = [];
|
||||
foreach (File::allFiles($directory) as $file) {
|
||||
$models[] = $namespace . str($file->getFilename())
|
||||
->replace([DIRECTORY_SEPARATOR, '.php'], ['\\', '']);
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
public static function discoverCustomEvents(): array
|
||||
{
|
||||
$directory = app_path('Events');
|
||||
|
||||
$events = [];
|
||||
foreach (File::allFiles($directory) as $file) {
|
||||
$namespace = str($file->getPath())
|
||||
->after(base_path())
|
||||
->replace(DIRECTORY_SEPARATOR, '\\')
|
||||
->replace('\\app\\', 'App\\')
|
||||
->toString();
|
||||
|
||||
$events[] = $namespace . '\\' . str($file->getFilename())
|
||||
->replace([DIRECTORY_SEPARATOR, '.php'], ['\\', '']);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\EggVariable;
|
||||
|
||||
class EggVariableObserver
|
||||
{
|
||||
public function creating(EggVariable $variable): void
|
||||
{
|
||||
if (isset($variable->field_type)) {
|
||||
unset($variable->field_type);
|
||||
}
|
||||
}
|
||||
|
||||
public function updating(EggVariable $variable): void
|
||||
{
|
||||
if (isset($variable->field_type)) {
|
||||
unset($variable->field_type);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Events;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
|
||||
class ServerObserver
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
/**
|
||||
* Listen to the Server creating event.
|
||||
*/
|
||||
public function creating(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Creating($server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Server created event.
|
||||
*/
|
||||
public function created(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Created($server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Server deleting event.
|
||||
*/
|
||||
public function deleting(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Deleting($server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Server deleted event.
|
||||
*/
|
||||
public function deleted(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Deleted($server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Server saving event.
|
||||
*/
|
||||
public function saving(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Saving($server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Server saved event.
|
||||
*/
|
||||
public function saved(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Saved($server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Server updating event.
|
||||
*/
|
||||
public function updating(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Updating($server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Server saved event.
|
||||
*/
|
||||
public function updated(Server $server): void
|
||||
{
|
||||
event(new Events\Server\Updated($server));
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Events;
|
||||
use App\Models\Subuser;
|
||||
use App\Notifications\AddedToServer;
|
||||
use App\Notifications\RemovedFromServer;
|
||||
|
||||
class SubuserObserver
|
||||
{
|
||||
/**
|
||||
* Listen to the Subuser creating event.
|
||||
*/
|
||||
public function creating(Subuser $subuser): void
|
||||
{
|
||||
event(new Events\Subuser\Creating($subuser));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Subuser created event.
|
||||
*/
|
||||
public function created(Subuser $subuser): void
|
||||
{
|
||||
event(new Events\Subuser\Created($subuser));
|
||||
|
||||
$subuser->user->notify(new AddedToServer([
|
||||
'user' => $subuser->user->name_first,
|
||||
'name' => $subuser->server->name,
|
||||
'uuid_short' => $subuser->server->uuid_short,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Subuser deleting event.
|
||||
*/
|
||||
public function deleting(Subuser $subuser): void
|
||||
{
|
||||
event(new Events\Subuser\Deleting($subuser));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Subuser deleted event.
|
||||
*/
|
||||
public function deleted(Subuser $subuser): void
|
||||
{
|
||||
event(new Events\Subuser\Deleted($subuser));
|
||||
|
||||
$subuser->user->notify(new RemovedFromServer([
|
||||
'user' => $subuser->user->name_first,
|
||||
'name' => $subuser->server->name,
|
||||
]));
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Events;
|
||||
use App\Models\User;
|
||||
|
||||
class UserObserver
|
||||
{
|
||||
protected string $uuid;
|
||||
|
||||
/**
|
||||
* Listen to the User creating event.
|
||||
*/
|
||||
public function creating(User $user): void
|
||||
{
|
||||
event(new Events\User\Creating($user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the User created event.
|
||||
*/
|
||||
public function created(User $user): void
|
||||
{
|
||||
event(new Events\User\Created($user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the User deleting event.
|
||||
*/
|
||||
public function deleting(User $user): void
|
||||
{
|
||||
event(new Events\User\Deleting($user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the User deleted event.
|
||||
*/
|
||||
public function deleted(User $user): void
|
||||
{
|
||||
event(new Events\User\Deleted($user));
|
||||
}
|
||||
}
|
@ -2,16 +2,7 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Server;
|
||||
use App\Models\Subuser;
|
||||
use App\Models\EggVariable;
|
||||
use App\Observers\UserObserver;
|
||||
use App\Observers\ServerObserver;
|
||||
use App\Observers\SubuserObserver;
|
||||
use App\Observers\EggVariableObserver;
|
||||
use App\Events\Server\Installed as ServerInstalledEvent;
|
||||
use App\Notifications\ServerInstalled as ServerInstalledNotification;
|
||||
use App\Listeners\DispatchWebhooks;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
@ -20,19 +11,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
* The event to listener mappings for the application.
|
||||
*/
|
||||
protected $listen = [
|
||||
ServerInstalledEvent::class => [ServerInstalledNotification::class],
|
||||
'App\\*' => [DispatchWebhooks::class],
|
||||
'eloquent.created*' => [DispatchWebhooks::class],
|
||||
'eloquent.deleted*' => [DispatchWebhooks::class],
|
||||
'eloquent.updated*' => [DispatchWebhooks::class],
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
User::observe(UserObserver::class);
|
||||
Server::observe(ServerObserver::class);
|
||||
Subuser::observe(SubuserObserver::class);
|
||||
EggVariable::observe(EggVariableObserver::class);
|
||||
}
|
||||
}
|
||||
|
@ -48,8 +48,7 @@ class EggExporterService
|
||||
],
|
||||
'variables' => $egg->variables->map(function (EggVariable $eggVariable) {
|
||||
return Collection::make($eggVariable->toArray())
|
||||
->except(['id', 'egg_id', 'created_at', 'updated_at'])
|
||||
->merge(['field_type' => 'text']);
|
||||
->except(['id', 'egg_id', 'created_at', 'updated_at']);
|
||||
}),
|
||||
];
|
||||
|
||||
|
@ -48,6 +48,11 @@ class EggImporterService
|
||||
'copy_script_from' => null,
|
||||
]);
|
||||
|
||||
// Don't check for this anymore
|
||||
for ($i = 0; $i < count($parsed['variables']); $i++) {
|
||||
unset($parsed['variables'][$i]['field_type']);
|
||||
}
|
||||
|
||||
$egg = $this->fillFromParsed($egg, $parsed);
|
||||
$egg->save();
|
||||
|
||||
@ -157,17 +162,13 @@ class EggImporterService
|
||||
$images = $parsed['images'];
|
||||
}
|
||||
|
||||
unset($parsed['images'], $parsed['image']);
|
||||
unset($parsed['images'], $parsed['image'], $parsed['field_type']);
|
||||
|
||||
$parsed['docker_images'] = [];
|
||||
foreach ($images as $image) {
|
||||
$parsed['docker_images'][$image] = $image;
|
||||
}
|
||||
|
||||
$parsed['variables'] = array_map(function ($value) {
|
||||
return array_merge($value, ['field_type' => 'text']);
|
||||
}, $parsed['variables']);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Services\Subusers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Notifications\AddedToServer;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\Server;
|
||||
use App\Models\Subuser;
|
||||
@ -59,11 +60,19 @@ class SubuserCreationService
|
||||
throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists'));
|
||||
}
|
||||
|
||||
return Subuser::query()->create([
|
||||
$subuser = Subuser::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'server_id' => $server->id,
|
||||
'permissions' => array_unique($permissions),
|
||||
]);
|
||||
|
||||
$subuser->user->notify(new AddedToServer([
|
||||
'user' => $subuser->user->name_first,
|
||||
'name' => $subuser->server->name,
|
||||
'uuid_short' => $subuser->server->uuid_short,
|
||||
]));
|
||||
|
||||
return $subuser;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
15
app/Traits/Services/HasWebhookPayload.php
Normal file
15
app/Traits/Services/HasWebhookPayload.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits\Services;
|
||||
|
||||
trait HasWebhookPayload
|
||||
{
|
||||
public function getPayload(): array
|
||||
{
|
||||
if (method_exists($this, '__serialize')) {
|
||||
return $this->__serialize();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
use App\Models\Allocation;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
@ -23,6 +24,7 @@ class AllocationFactory extends Factory
|
||||
return [
|
||||
'ip' => $this->faker->unique()->ipv4(),
|
||||
'port' => $this->faker->unique()->numberBetween(1024, 65535),
|
||||
'node_id' => Node::factory(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,12 @@ class EggFactory extends Factory
|
||||
{
|
||||
return [
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
'author' => $this->faker->email(),
|
||||
'docker_images' => ['a', 'b', 'c'],
|
||||
'config_logs' => '{}',
|
||||
'config_startup' => '{}',
|
||||
'config_stop' => '{}',
|
||||
'config_files' => '{}',
|
||||
'name' => $this->faker->name(),
|
||||
'description' => implode(' ', $this->faker->sentences()),
|
||||
'startup' => 'java -jar test.jar',
|
||||
|
@ -40,6 +40,7 @@ class NodeFactory extends Factory
|
||||
'daemon_listen' => 8080,
|
||||
'daemon_sftp' => 2022,
|
||||
'daemon_base' => '/var/lib/panel/volumes',
|
||||
'maintenance_mode' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Node;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Support\Str;
|
||||
@ -17,12 +21,28 @@ class ServerFactory extends Factory
|
||||
*/
|
||||
protected $model = Server::class;
|
||||
|
||||
public function withNode(?Node $node = null): static
|
||||
{
|
||||
$node ??= Node::factory()->create();
|
||||
|
||||
return $this->state(fn () => [
|
||||
'node_id' => $node->id,
|
||||
'allocation_id' => Allocation::factory([
|
||||
'node_id' => $node->id,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'owner_id' => User::factory(),
|
||||
'node_id' => Node::factory(),
|
||||
'allocation_id' => Allocation::factory(),
|
||||
'egg_id' => Egg::factory(),
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
'uuid_short' => Str::lower(Str::random(8)),
|
||||
'name' => $this->faker->firstName(),
|
||||
|
20
database/Factories/WebhookConfigurationFactory.php
Normal file
20
database/Factories/WebhookConfigurationFactory.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class WebhookConfigurationFactory extends Factory
|
||||
{
|
||||
protected $model = WebhookConfiguration::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'endpoint' => fake()->url(),
|
||||
'description' => fake()->sentence(),
|
||||
'events' => [],
|
||||
];
|
||||
}
|
||||
}
|
@ -37,7 +37,6 @@ class EggSeeder extends Seeder
|
||||
public function run(): void
|
||||
{
|
||||
foreach (static::$imports as $import) {
|
||||
/* @noinspection PhpParamsInspection */
|
||||
$this->parseEggFiles($import);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('webhook_configurations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('endpoint');
|
||||
$table->string('description');
|
||||
$table->json('events');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('webhook_configurations');
|
||||
}
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('webhooks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(\App\Models\WebhookConfiguration::class)->constrained();
|
||||
$table->string('event');
|
||||
$table->string('endpoint');
|
||||
$table->timestamp('successful_at')->nullable();
|
||||
$table->json('payload');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('webhooks');
|
||||
}
|
||||
};
|
69
tests/Feature/DispatchWebhooksTest.php
Normal file
69
tests/Feature/DispatchWebhooksTest.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Feature;
|
||||
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use App\Models\Server;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
class DispatchWebhooksTest extends TestCase
|
||||
{
|
||||
use LazilyRefreshDatabase;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Queue::fake();
|
||||
}
|
||||
|
||||
public function test_it_sends_a_single_webhook(): void
|
||||
{
|
||||
WebhookConfiguration::factory()->create([
|
||||
'events' => ['eloquent.created: '.Server::class],
|
||||
]);
|
||||
|
||||
$this->createServer();
|
||||
|
||||
Queue::assertPushed(ProcessWebhook::class);
|
||||
}
|
||||
|
||||
public function test_sends_multiple_webhooks()
|
||||
{
|
||||
WebhookConfiguration::factory(2)
|
||||
->create(['events' => ['eloquent.created: '.Server::class]]);
|
||||
|
||||
$this->createServer();
|
||||
|
||||
Queue::assertPushed(ProcessWebhook::class, 2);
|
||||
}
|
||||
|
||||
public function test_it_sends_no_webhooks()
|
||||
{
|
||||
WebhookConfiguration::factory()->create();
|
||||
|
||||
$this->createServer();
|
||||
|
||||
Queue::assertNothingPushed();
|
||||
}
|
||||
|
||||
public function test_it_sends_some_webhooks()
|
||||
{
|
||||
WebhookConfiguration::factory(2)
|
||||
->sequence(
|
||||
['events' => ['eloquent.created: '.Server::class]],
|
||||
['events' => ['eloquent.deleted: '.Server::class]]
|
||||
)->create();
|
||||
|
||||
$this->createServer();
|
||||
|
||||
Queue::assertPushed(ProcessWebhook::class, 1);
|
||||
}
|
||||
|
||||
public function createServer(): Server
|
||||
{
|
||||
return Server::factory()->withNode()->create();
|
||||
}
|
||||
}
|
204
tests/Feature/ProcessWebhooksTest.php
Normal file
204
tests/Feature/ProcessWebhooksTest.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Feature;
|
||||
|
||||
use App\Events\Server\Installed;
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use App\Models\Server;
|
||||
use App\Models\Webhook;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
||||
use Illuminate\Http\Client\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ProcessWebhooksTest extends TestCase
|
||||
{
|
||||
use LazilyRefreshDatabase;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Http::preventStrayRequests();
|
||||
Carbon::setTestNow();
|
||||
}
|
||||
|
||||
public function test_it_sends_a_single_webhook(): void
|
||||
{
|
||||
$webhook = WebhookConfiguration::factory()->create([
|
||||
'events' => [$eventName = 'eloquent.created: '.Server::class],
|
||||
]);
|
||||
|
||||
Http::fake([$webhook->endpoint => Http::response()]);
|
||||
|
||||
$data = [
|
||||
'status' => null,
|
||||
'oom_killer' => false,
|
||||
'installed_at' => null,
|
||||
'owner_id' => 1,
|
||||
'node_id' => 1,
|
||||
'allocation_id' => 1,
|
||||
'egg_id' => 1,
|
||||
'uuid' => '9ff9885f-ab79-4a6e-a53e-466a84cdb2d8',
|
||||
'uuid_short' => 'ypk27val',
|
||||
'name' => 'Delmer',
|
||||
'description' => 'Est sed quibusdam sed eos quae est. Ut similique non impedit voluptas. Aperiam repellendus impedit voluptas officiis id.',
|
||||
'skip_scripts' => false,
|
||||
'memory' => 512,
|
||||
'swap' => 0,
|
||||
'disk' => 512,
|
||||
'io' => 500,
|
||||
'cpu' => 0,
|
||||
'threads' => null,
|
||||
'startup' => '/bin/bash echo "hello world"',
|
||||
'image' => 'foo/bar:latest',
|
||||
'allocation_limit' => null,
|
||||
'database_limit' => null,
|
||||
'backup_limit' => 0,
|
||||
'created_at' => '2024-09-12T20:21:29.000000Z',
|
||||
'updated_at' => '2024-09-12T20:21:29.000000Z',
|
||||
'id' => 1,
|
||||
];
|
||||
|
||||
ProcessWebhook::dispatchSync(
|
||||
$webhook,
|
||||
'eloquent.created: '.Server::class,
|
||||
$data,
|
||||
);
|
||||
|
||||
$this->assertCount(1, cache()->get("webhooks.$eventName"));
|
||||
$this->assertEquals($webhook->id, cache()->get("webhooks.$eventName")->first()->id);
|
||||
|
||||
Http::assertSentCount(1);
|
||||
Http::assertSent(function (Request $request) use ($webhook, $data) {
|
||||
return $webhook->endpoint === $request->url()
|
||||
&& $request->data() === $data;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_sends_multiple_webhooks()
|
||||
{
|
||||
[$webhook1, $webhook2] = WebhookConfiguration::factory(2)
|
||||
->create(['events' => [$eventName = 'eloquent.created: '.Server::class]]);
|
||||
|
||||
Http::fake([
|
||||
$webhook1->endpoint => Http::response(),
|
||||
$webhook2->endpoint => Http::response(),
|
||||
]);
|
||||
|
||||
$this->createServer();
|
||||
|
||||
$this->assertCount(2, cache()->get("webhooks.$eventName"));
|
||||
$this->assertContains($webhook1->id, cache()->get("webhooks.$eventName")->pluck('id'));
|
||||
$this->assertContains($webhook2->id, cache()->get("webhooks.$eventName")->pluck('id'));
|
||||
|
||||
Http::assertSentCount(2);
|
||||
Http::assertSent(fn (Request $request) => $webhook1->endpoint === $request->url());
|
||||
Http::assertSent(fn (Request $request) => $webhook2->endpoint === $request->url());
|
||||
}
|
||||
|
||||
public function test_it_sends_no_webhooks()
|
||||
{
|
||||
Http::fake();
|
||||
|
||||
WebhookConfiguration::factory()->create();
|
||||
|
||||
$this->createServer();
|
||||
|
||||
Http::assertSentCount(0);
|
||||
}
|
||||
|
||||
public function test_it_sends_some_webhooks()
|
||||
{
|
||||
[$webhook1, $webhook2] = WebhookConfiguration::factory(2)
|
||||
->sequence(
|
||||
['events' => ['eloquent.created: '.Server::class]],
|
||||
['events' => ['eloquent.deleted: '.Server::class]]
|
||||
)->create();
|
||||
|
||||
Http::fake([
|
||||
$webhook1->endpoint => Http::response(),
|
||||
$webhook2->endpoint => Http::response(),
|
||||
]);
|
||||
|
||||
$this->createServer();
|
||||
|
||||
Http::assertSentCount(1);
|
||||
Http::assertSent(fn (Request $request) => $webhook1->endpoint === $request->url());
|
||||
Http::assertNotSent(fn (Request $request) => $webhook2->endpoint === $request->url());
|
||||
}
|
||||
|
||||
public function test_it_records_when_a_webhook_is_sent()
|
||||
{
|
||||
$webhookConfig = WebhookConfiguration::factory()
|
||||
->create(['events' => ['eloquent.created: '.Server::class]]);
|
||||
|
||||
Http::fake([$webhookConfig->endpoint => Http::response()]);
|
||||
|
||||
$this->assertDatabaseCount(Webhook::class, 0);
|
||||
|
||||
$server = $this->createServer();
|
||||
|
||||
$this->assertDatabaseCount(Webhook::class, 1);
|
||||
|
||||
$webhook = Webhook::query()->first();
|
||||
$this->assertEquals($server->uuid, $webhook->payload[0]['uuid']);
|
||||
|
||||
$this->assertDatabaseHas(Webhook::class, [
|
||||
'endpoint' => $webhookConfig->endpoint,
|
||||
'successful_at' => now()->startOfSecond(),
|
||||
'event' => 'eloquent.created: '.Server::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_records_when_a_webhook_fails()
|
||||
{
|
||||
$webhookConfig = WebhookConfiguration::factory()->create([
|
||||
'events' => ['eloquent.created: '.Server::class],
|
||||
]);
|
||||
|
||||
Http::fake([$webhookConfig->endpoint => Http::response(status: 500)]);
|
||||
|
||||
$this->assertDatabaseCount(Webhook::class, 0);
|
||||
|
||||
$server = $this->createServer();
|
||||
|
||||
$this->assertDatabaseCount(Webhook::class, 1);
|
||||
$this->assertDatabaseHas(Webhook::class, [
|
||||
'payload' => json_encode([$server->toArray()]),
|
||||
'endpoint' => $webhookConfig->endpoint,
|
||||
'successful_at' => null,
|
||||
'event' => 'eloquent.created: '.Server::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_is_triggered_on_custom_events()
|
||||
{
|
||||
$webhookConfig = WebhookConfiguration::factory()->create([
|
||||
'events' => [Installed::class],
|
||||
]);
|
||||
|
||||
Http::fake([$webhookConfig->endpoint => Http::response()]);
|
||||
|
||||
$this->assertDatabaseCount(Webhook::class, 0);
|
||||
|
||||
$server = $this->createServer();
|
||||
|
||||
event(new Installed($server));
|
||||
|
||||
$this->assertDatabaseCount(Webhook::class, 1);
|
||||
$this->assertDatabaseHas(Webhook::class, [
|
||||
// 'payload' => json_encode([['server' => $server->toArray()]]),
|
||||
'endpoint' => $webhookConfig->endpoint,
|
||||
'successful_at' => now()->startOfSecond(),
|
||||
'event' => Installed::class,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function createServer(): Server
|
||||
{
|
||||
return Server::factory()->withNode()->create();
|
||||
}
|
||||
}
|
@ -19,6 +19,9 @@ abstract class TestCase extends BaseTestCase
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
CarbonImmutable::setTestNow(Carbon::now());
|
||||
|
||||
// TODO: if unit tests suite, then force set DB_HOST=UNIT_NO_DB
|
||||
// env('DB_DATABASE', 'UNIT_NO_DB');
|
||||
|
||||
// Why, you ask? If we don't force this to false it is possible for certain exceptions
|
||||
// to show their error message properly in the integration test output, but not actually
|
||||
// be setup correctly to display their message in production.
|
||||
|
@ -29,10 +29,14 @@ class MaintenanceMiddlewareTest extends MiddlewareTestCase
|
||||
*/
|
||||
public function testHandle(): void
|
||||
{
|
||||
$server = Server::factory()->make();
|
||||
$node = Node::factory()->make(['maintenance' => 0]);
|
||||
// maintenance mode is off by default
|
||||
$server = new Server();
|
||||
|
||||
$node = new Node([
|
||||
'maintenance_mode' => false,
|
||||
]);
|
||||
$server->setRelation('node', $node);
|
||||
|
||||
$this->setRequestAttribute('server', $server);
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
||||
@ -43,10 +47,13 @@ class MaintenanceMiddlewareTest extends MiddlewareTestCase
|
||||
*/
|
||||
public function testHandleInMaintenanceMode(): void
|
||||
{
|
||||
$server = Server::factory()->make();
|
||||
$node = Node::factory()->make(['maintenance_mode' => 1]);
|
||||
$server = new Server();
|
||||
|
||||
$node = new Node([
|
||||
'maintenance_mode' => true,
|
||||
]);
|
||||
$server->setRelation('node', $node);
|
||||
|
||||
$this->setRequestAttribute('server', $server);
|
||||
|
||||
$this->response->shouldReceive('view')
|
||||
|
Loading…
x
Reference in New Issue
Block a user