feat/reverb (#20)
Co-authored-by: jon brookes <marshyon@gmail.com> Reviewed-on: https://codeberg.org/headshed/share-lt/pulls/20
This commit is contained in:
parent
74bc17d019
commit
21147af908
30 changed files with 1948 additions and 29 deletions
3
.github/copilot-instructions.md
vendored
3
.github/copilot-instructions.md
vendored
|
|
@ -13,6 +13,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
|
|||
- laravel/fortify (FORTIFY) - v1
|
||||
- laravel/framework (LARAVEL) - v12
|
||||
- laravel/prompts (PROMPTS) - v0
|
||||
- laravel/reverb (REVERB) - v1
|
||||
- laravel/sanctum (SANCTUM) - v4
|
||||
- livewire/flux (FLUXUI_FREE) - v2
|
||||
- livewire/livewire (LIVEWIRE) - v3
|
||||
|
|
@ -507,4 +508,4 @@ Fortify is a headless authentication backend that provides authentication routes
|
|||
- Only implement what solves the immediate problem.
|
||||
- Ask before adding optional infrastructure or configuration sections.
|
||||
- If a system worked before without something, don't add it "just in case".
|
||||
- Minimize configuration, complexity, and dependencies.
|
||||
- Minimize configuration, complexity, and dependencies.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
|
|||
- laravel/fortify (FORTIFY) - v1
|
||||
- laravel/framework (LARAVEL) - v12
|
||||
- laravel/prompts (PROMPTS) - v0
|
||||
- laravel/reverb (REVERB) - v1
|
||||
- laravel/sanctum (SANCTUM) - v4
|
||||
- livewire/flux (FLUXUI_FREE) - v2
|
||||
- livewire/livewire (LIVEWIRE) - v3
|
||||
|
|
|
|||
38
app/Console/Commands/SendPreviewSiteBuiltNotification.php
Normal file
38
app/Console/Commands/SendPreviewSiteBuiltNotification.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\PreviewSiteBuilt;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SendPreviewSiteBuiltNotification extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'test:preview-site-built {--message=Preview site is built}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send a test notification that the preview site is built';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$message = $this->option('message');
|
||||
|
||||
$this->info("Command :: Broadcasting preview site built notification: {$message}");
|
||||
|
||||
PreviewSiteBuilt::dispatch($message, 'success');
|
||||
|
||||
$this->info('Notification broadcasted successfully!');
|
||||
$this->info('Check your Filament admin panel for the toast notification.');
|
||||
}
|
||||
}
|
||||
31
app/Events/PreviewSiteBuilt.php
Normal file
31
app/Events/PreviewSiteBuilt.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PreviewSiteBuilt implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public string $message = 'Preview site is built',
|
||||
public string $type = 'success'
|
||||
) {}
|
||||
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new Channel('filament-notifications'),
|
||||
];
|
||||
}
|
||||
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'preview-site.built';
|
||||
}
|
||||
}
|
||||
30
app/Events/TestEvent.php
Normal file
30
app/Events/TestEvent.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TestEvent implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public string $message = 'Test message from Laravel'
|
||||
) {}
|
||||
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new Channel('test-channel'),
|
||||
];
|
||||
}
|
||||
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'test.message';
|
||||
}
|
||||
}
|
||||
28
app/Http/Controllers/NotificationController.php
Normal file
28
app/Http/Controllers/NotificationController.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\PreviewSiteBuilt;
|
||||
use App\Http\Requests\SendPreviewSiteBuiltNotificationRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send preview site built notification.
|
||||
*/
|
||||
public function sendPreviewSiteBuilt(SendPreviewSiteBuiltNotificationRequest $request): JsonResponse
|
||||
{
|
||||
$message = $request->validated()['message'];
|
||||
|
||||
PreviewSiteBuilt::dispatch($message, 'success');
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Preview site built notification sent successfully',
|
||||
'data' => [
|
||||
'notification_message' => $message,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SendPreviewSiteBuiltNotificationRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() && $this->user()->email === config('app.admin_email');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'message' => 'required|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -71,5 +71,155 @@ class AdminPanelProvider extends PanelProvider
|
|||
PanelsRenderHook::BODY_END,
|
||||
fn(): string => \Illuminate\Support\Facades\Blade::render('@vite("resources/js/app.js")'),
|
||||
);
|
||||
|
||||
FilamentView::registerRenderHook(
|
||||
PanelsRenderHook::BODY_END,
|
||||
function (): string {
|
||||
return '
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (window.Echo) {
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> 4143902e603c7e3e25de5e5f49c1116dd5a5743b
|
||||
console.log("Setting up Filament Reverb notifications...");
|
||||
console.log("Available globals:", {
|
||||
FilamentNotification: typeof FilamentNotification,
|
||||
Livewire: typeof window.Livewire,
|
||||
filament: typeof window.filament
|
||||
});
|
||||
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> 4143902e603c7e3e25de5e5f49c1116dd5a5743b
|
||||
// Listen for preview site built events
|
||||
window.Echo.channel("filament-notifications")
|
||||
.listen("preview-site.built", function(event) {
|
||||
console.log("🎉 Received preview site built event:", event);
|
||||
|
||||
// Use Filament v4 notification system
|
||||
if (typeof FilamentNotification !== "undefined") {
|
||||
new FilamentNotification()
|
||||
.title(event.message)
|
||||
.success()
|
||||
.duration(5000)
|
||||
.send();
|
||||
console.log("✅ Sent via FilamentNotification v4");
|
||||
} else {
|
||||
console.warn("FilamentNotification not available");
|
||||
// Fallback notification
|
||||
const notification = document.createElement("div");
|
||||
notification.style.cssText = `
|
||||
position: fixed !important;
|
||||
top: 20px !important;
|
||||
right: 20px !important;
|
||||
z-index: 999999 !important;
|
||||
background: #10b981 !important;
|
||||
color: white !important;
|
||||
padding: 16px 20px !important;
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.3) !important;
|
||||
font-family: system-ui, -apple-system, sans-serif !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 500 !important;
|
||||
max-width: 350px !important;
|
||||
display: block !important;
|
||||
opacity: 1 !important;
|
||||
`;
|
||||
|
||||
notification.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<div style="margin-right: 15px;">
|
||||
✓ ${event.message}
|
||||
</div>
|
||||
<button onclick="this.parentElement.parentElement.remove()"
|
||||
style="background: none; border: none; color: white; font-size: 18px; cursor: pointer; padding: 0;">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
console.log("✅ Created fallback notification");
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
})
|
||||
.listen(".preview-site.built", function(event) {
|
||||
console.log("🔄 Also listening with dot prefix (the working one):", event);
|
||||
<<<<<<< HEAD
|
||||
console.log("message:", event.message);
|
||||
=======
|
||||
>>>>>>> 4143902e603c7e3e25de5e5f49c1116dd5a5743b
|
||||
|
||||
// Use Filament v4 notification system
|
||||
if (typeof FilamentNotification !== "undefined") {
|
||||
new FilamentNotification()
|
||||
.title(event.message)
|
||||
.success()
|
||||
.duration(5000)
|
||||
.send();
|
||||
console.log("✅ Sent via FilamentNotification v4 (from dot prefix)");
|
||||
} else {
|
||||
console.warn("FilamentNotification not available, creating fallback");
|
||||
// Fallback notification
|
||||
const notification = document.createElement("div");
|
||||
notification.style.cssText = `
|
||||
position: fixed !important;
|
||||
top: 20px !important;
|
||||
right: 20px !important;
|
||||
z-index: 999999 !important;
|
||||
background: #3b82f6 !important;
|
||||
color: white !important;
|
||||
padding: 16px 20px !important;
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.3) !important;
|
||||
font-family: system-ui, -apple-system, sans-serif !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 500 !important;
|
||||
max-width: 350px !important;
|
||||
display: block !important;
|
||||
opacity: 1 !important;
|
||||
`;
|
||||
|
||||
notification.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<div style="margin-right: 15px;">
|
||||
🔄 ${event.message}
|
||||
</div>
|
||||
<button onclick="this.parentElement.parentElement.remove()"
|
||||
style="background: none; border: none; color: white; font-size: 18px; cursor: pointer; padding: 0;">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
console.log("✅ Created dot prefix fallback notification");
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("✅ Event listeners set up for filament-notifications channel");
|
||||
} else {
|
||||
console.error("❌ Echo is not available for Filament notifications");
|
||||
}
|
||||
});
|
||||
</script>';
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
channels: __DIR__.'/../routes/channels.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
LARAVEL_CONTAINER_NAME="quay.io/marshyon/share-lt"
|
||||
CONTAINER_LABEL="v0.0.6"
|
||||
CONTAINER_LABEL="v0.0.7"
|
||||
CACHE="--no-cache"
|
||||
# CACHE=""
|
||||
|
||||
|
|
|
|||
11
cmd/curl_send_user_toast_message.sh
Executable file
11
cmd/curl_send_user_toast_message.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
URL='http://127.0.0.1:8000'
|
||||
|
||||
curl -X POST $URL/api/notifications/preview-site-built \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message": "Published to LIVE 💯🚀🎯 - site notification!"}'
|
||||
|
||||
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ if [ ! -d /var/www/public/storage ]; then
|
|||
fi
|
||||
|
||||
php artisan config:clear
|
||||
|
||||
npm run build
|
||||
|
||||
# Start supervisord directly
|
||||
exec "$@"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"filament/spatie-laravel-tags-plugin": "^4.0",
|
||||
"laravel/fortify": "^1.30",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/reverb": "^1.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
|
|
|
|||
1004
composer.lock
generated
1004
composer.lock
generated
File diff suppressed because it is too large
Load diff
82
config/broadcasting.php
Normal file
82
config/broadcasting.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Broadcaster
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default broadcaster that will be used by the
|
||||
| framework when an event needs to be broadcast. You may set this to
|
||||
| any of the connections defined in the "connections" array below.
|
||||
|
|
||||
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('BROADCAST_CONNECTION', 'null'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Broadcast Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the broadcast connections that will be used
|
||||
| to broadcast events to other systems or over WebSockets. Samples of
|
||||
| each available type of connection are provided inside this array.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'reverb' => [
|
||||
'driver' => 'reverb',
|
||||
'key' => env('REVERB_APP_KEY'),
|
||||
'secret' => env('REVERB_APP_SECRET'),
|
||||
'app_id' => env('REVERB_APP_ID'),
|
||||
'options' => [
|
||||
'host' => env('REVERB_HOST'),
|
||||
'port' => env('REVERB_PORT', 443),
|
||||
'scheme' => env('REVERB_SCHEME', 'https'),
|
||||
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'client_options' => [
|
||||
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||
],
|
||||
],
|
||||
|
||||
'pusher' => [
|
||||
'driver' => 'pusher',
|
||||
'key' => env('PUSHER_APP_KEY'),
|
||||
'secret' => env('PUSHER_APP_SECRET'),
|
||||
'app_id' => env('PUSHER_APP_ID'),
|
||||
'options' => [
|
||||
'cluster' => env('PUSHER_APP_CLUSTER'),
|
||||
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||
'port' => env('PUSHER_PORT', 443),
|
||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||
'encrypted' => true,
|
||||
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'client_options' => [
|
||||
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||
],
|
||||
],
|
||||
|
||||
'ably' => [
|
||||
'driver' => 'ably',
|
||||
'key' => env('ABLY_KEY'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'driver' => 'log',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'null',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
|
@ -16,18 +16,18 @@ return [
|
|||
|
||||
'broadcasting' => [
|
||||
|
||||
// 'echo' => [
|
||||
// 'broadcaster' => 'pusher',
|
||||
// 'key' => env('VITE_PUSHER_APP_KEY'),
|
||||
// 'cluster' => env('VITE_PUSHER_APP_CLUSTER'),
|
||||
// 'wsHost' => env('VITE_PUSHER_HOST'),
|
||||
// 'wsPort' => env('VITE_PUSHER_PORT'),
|
||||
// 'wssPort' => env('VITE_PUSHER_PORT'),
|
||||
// 'authEndpoint' => '/broadcasting/auth',
|
||||
// 'disableStats' => true,
|
||||
// 'encrypted' => true,
|
||||
// 'forceTLS' => true,
|
||||
// ],
|
||||
'echo' => [
|
||||
'broadcaster' => 'reverb',
|
||||
'key' => env('VITE_REVERB_APP_KEY'),
|
||||
'cluster' => env('VITE_REVERB_APP_CLUSTER'),
|
||||
'wsHost' => env('VITE_REVERB_HOST'),
|
||||
'wsPort' => env('VITE_REVERB_PORT'),
|
||||
'wssPort' => env('VITE_REVERB_PORT'),
|
||||
'authEndpoint' => '/broadcasting/auth',
|
||||
'disableStats' => true,
|
||||
'encrypted' => env('VITE_REVERB_SCHEME', 'https') === 'https',
|
||||
'forceTLS' => env('VITE_REVERB_SCHEME', 'https') === 'https',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
|
|
|
|||
95
config/reverb.php
Normal file
95
config/reverb.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Reverb Server
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default server used by Reverb to handle
|
||||
| incoming messages as well as broadcasting message to all your
|
||||
| connected clients. At this time only "reverb" is supported.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('REVERB_SERVER', 'reverb'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Reverb Servers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define details for each of the supported Reverb servers.
|
||||
| Each server has its own configuration options that are defined in
|
||||
| the array below. You should ensure all the options are present.
|
||||
|
|
||||
*/
|
||||
|
||||
'servers' => [
|
||||
|
||||
'reverb' => [
|
||||
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
|
||||
'port' => env('REVERB_SERVER_PORT', 8080),
|
||||
'path' => env('REVERB_SERVER_PATH', ''),
|
||||
'hostname' => env('REVERB_HOST'),
|
||||
'options' => [
|
||||
'tls' => [],
|
||||
],
|
||||
'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
|
||||
'scaling' => [
|
||||
'enabled' => env('REVERB_SCALING_ENABLED', false),
|
||||
'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
|
||||
'server' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
'timeout' => env('REDIS_TIMEOUT', 60),
|
||||
],
|
||||
],
|
||||
'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
|
||||
'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Reverb Applications
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define how Reverb applications are managed. If you choose
|
||||
| to use the "config" provider, you may define an array of apps which
|
||||
| your server will support, including their connection credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'apps' => [
|
||||
|
||||
'provider' => 'config',
|
||||
|
||||
'apps' => [
|
||||
[
|
||||
'key' => env('REVERB_APP_KEY'),
|
||||
'secret' => env('REVERB_APP_SECRET'),
|
||||
'app_id' => env('REVERB_APP_ID'),
|
||||
'options' => [
|
||||
'host' => env('REVERB_HOST'),
|
||||
'port' => env('REVERB_PORT', 443),
|
||||
'scheme' => env('REVERB_SCHEME', 'https'),
|
||||
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'allowed_origins' => ['*'],
|
||||
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
|
||||
'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
|
||||
'max_connections' => env('REVERB_APP_MAX_CONNECTIONS'),
|
||||
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
|
@ -33,3 +33,14 @@ stdout_logfile_maxbytes=0
|
|||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
stopwaitsecs=3600
|
||||
|
||||
[program:reverb]
|
||||
command=/usr/local/bin/php /var/www/artisan reverb:start
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=3
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
stopwaitsecs=3600
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
services:
|
||||
nats:
|
||||
image: nats:2.9.19-alpine
|
||||
restart: unless-stopped
|
||||
#nats:
|
||||
# image: nats:2.9.19-alpine
|
||||
# restart: unless-stopped
|
||||
#ports:
|
||||
# - 4222:4222
|
||||
# - 8222:8222
|
||||
volumes:
|
||||
- ./nats/nats-server.conf:/nats-server.conf
|
||||
- nats-data:/opt/storage
|
||||
command: ["-c", "/nats-server.conf"]
|
||||
# volumes:
|
||||
# - ./nats/nats-server.conf:/nats-server.conf
|
||||
# - nats-data:/opt/storage
|
||||
# command: ["-c", "/nats-server.conf"]
|
||||
|
||||
networks:
|
||||
- app-network
|
||||
- nats
|
||||
# networks:
|
||||
# - app-network
|
||||
# - nats
|
||||
|
||||
# nats-cli:
|
||||
# image: natsio/nats-box
|
||||
|
|
@ -25,7 +25,7 @@ services:
|
|||
# - nats
|
||||
|
||||
app:
|
||||
image: quay.io/marshyon/share-lt:v0.0.6
|
||||
image: quay.io/marshyon/share-lt:v0.0.7
|
||||
restart: unless-stopped
|
||||
tty: false
|
||||
working_dir: /var/www
|
||||
|
|
@ -84,12 +84,26 @@ services:
|
|||
- "AWS_DIRECTORY=${AWS_DIRECTORY}"
|
||||
- "MEDIA_DISK=${MEDIA_DISK}"
|
||||
- "VITE_APP_NAME=${APP_NAME}"
|
||||
|
||||
- "NATS_URL=${NATS_URL}"
|
||||
- "NATS_USERNAME=${NATS_USERNAME}"
|
||||
- "NATS_PASSWORD=${NATS_PASSWORD}"
|
||||
- "NATS_STREAM=${NATS_STREAM}"
|
||||
- "NATS_SUBJECT=${NATS_SUBJECT}"
|
||||
|
||||
- "REVERB_APP_ID=${REVERB_APP_ID}"
|
||||
- "REVERB_APP_KEY=${REVERB_APP_KEY}"
|
||||
- "REVERB_APP_SECRET=${REVERB_APP_SECRET}"
|
||||
- "REVERB_HOST=${REVERB_HOST}"
|
||||
- "REVERB_PORT=${REVERB_PORT}"
|
||||
- "REVERB_SCHEME=${REVERB_SCHEME}"
|
||||
- "REVERB_SERVER_HOST=${REVERB_SERVER_HOST}"
|
||||
- "REVERB_SERVER_PORT=${REVERB_SERVER_PORT}"
|
||||
- "VITE_REVERB_APP_KEY=${REVERB_APP_KEY}"
|
||||
- "VITE_REVERB_HOST=${REVERB_HOST}"
|
||||
- "VITE_REVERB_PORT=${REVERB_PORT}"
|
||||
- "VITE_REVERB_SCHEME=${REVERB_SCHEME}"
|
||||
- "BROADCAST_CONNECTION=${BROADCAST_CONNECTION}"
|
||||
|
||||
volumes:
|
||||
storage-data:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,25 @@ server {
|
|||
# access_log /var/log/nginx/access.log;
|
||||
access_log /dev/stdout;
|
||||
root /var/www/public;
|
||||
|
||||
|
||||
location ~ ^/(app|apps) {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
|
||||
# This is the "Don't Crash" magic for Reverb
|
||||
# It tells Reverb the original connection was HTTPS
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# Forward to Reverb's internal port
|
||||
proxy_pass http://127.0.0.1:9001;
|
||||
}
|
||||
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
|
|
|
|||
163
package-lock.json
generated
163
package-lock.json
generated
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "share-lt-recovery-zone",
|
||||
"name": "share-lt",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
@ -13,6 +13,10 @@
|
|||
"tailwindcss": "^4.0.7",
|
||||
"vite": "^7.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"laravel-echo": "^2.3.0",
|
||||
"pusher-js": "^8.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
|
||||
|
|
@ -755,6 +759,14 @@
|
|||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
|
||||
|
|
@ -1272,6 +1284,25 @@
|
|||
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
|
|
@ -1316,6 +1347,32 @@
|
|||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
|
||||
"integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.18.3",
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.2",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
|
||||
|
|
@ -1643,6 +1700,20 @@
|
|||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-echo": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.3.0.tgz",
|
||||
"integrity": "sha512-wgHPnnBvfHmu2I58xJ4asZH37Nu6P0472ku6zuoGRLc3zEWwIbpovDLYTiOshDH1SM7rA6AjZTKuu+jYoM1tpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pusher-js": "*",
|
||||
"socket.io-client": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.0.tgz",
|
||||
|
|
@ -1971,6 +2042,14 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
|
|
@ -2062,6 +2141,16 @@
|
|||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pusher-js": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz",
|
||||
"integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
|
@ -2144,6 +2233,38 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
|
||||
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1",
|
||||
"engine.io-client": "~6.6.1",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
|
||||
"integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
|
|
@ -2257,6 +2378,13 @@
|
|||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||
"dev": true,
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
|
|
@ -2400,6 +2528,39 @@
|
|||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,9 @@
|
|||
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
|
||||
"lightningcss-linux-x64-gnu": "^1.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"laravel-echo": "^2.3.0",
|
||||
"pusher-js": "^8.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.on('insert-editor-content', (data) => {
|
||||
// console.log('Received insert-editor-content data:', data);
|
||||
|
|
@ -44,3 +43,11 @@ document.addEventListener('livewire:init', () => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allow your team to quickly build robust real-time web applications.
|
||||
*/
|
||||
|
||||
import './echo';
|
||||
|
|
|
|||
28
resources/js/echo.js
Normal file
28
resources/js/echo.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import Echo from 'laravel-echo';
|
||||
import Pusher from 'pusher-js';
|
||||
|
||||
window.Pusher = Pusher;
|
||||
|
||||
// Add error handling and logging
|
||||
try {
|
||||
console.log('Initializing Echo with:', {
|
||||
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||
wsPort: import.meta.env.VITE_REVERB_PORT,
|
||||
scheme: import.meta.env.VITE_REVERB_SCHEME
|
||||
});
|
||||
|
||||
window.Echo = new Echo({
|
||||
broadcaster: 'reverb',
|
||||
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
||||
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
||||
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
});
|
||||
|
||||
console.log('Echo initialized:', window.Echo);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Echo:', error);
|
||||
}
|
||||
50
resources/views/test-reverb.blade.php
Normal file
50
resources/views/test-reverb.blade.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Reverb Test</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Reverb Connection Test</h1>
|
||||
<div id="status">Waiting for Echo...</div>
|
||||
<div id="messages"></div>
|
||||
|
||||
<script>
|
||||
const status = document.getElementById('status');
|
||||
const messages = document.getElementById('messages');
|
||||
|
||||
// Wait for Echo to be ready
|
||||
function checkEcho() {
|
||||
console.log('Checking Echo:', window.Echo);
|
||||
|
||||
if (window.Echo && window.Echo !== null) {
|
||||
status.textContent = 'Echo ready, connecting...';
|
||||
status.style.color = 'blue';
|
||||
|
||||
try {
|
||||
window.Echo.channel('test-channel')
|
||||
.listen('test.message', (e) => {
|
||||
console.log('Received message:', e);
|
||||
messages.innerHTML += `<p>Received: ${e.message}</p>`;
|
||||
});
|
||||
|
||||
status.textContent = 'Connected to Reverb!';
|
||||
status.style.color = 'green';
|
||||
} catch (error) {
|
||||
status.textContent = `Connection failed: ${error.message}`;
|
||||
status.style.color = 'red';
|
||||
console.error('Echo error:', error);
|
||||
}
|
||||
} else {
|
||||
setTimeout(checkEcho, 100); // Check again in 100ms
|
||||
}
|
||||
}
|
||||
|
||||
// Start checking after DOM loads
|
||||
document.addEventListener('DOMContentLoaded', checkEcho);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Http\Controllers\CategoryController;
|
||||
use App\Http\Controllers\EntryController;
|
||||
use App\Http\Controllers\NotificationController;
|
||||
use App\Http\Controllers\TextWidgetController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -23,4 +24,6 @@ Route::middleware('auth:sanctum')->group(function () {
|
|||
Route::get('/text-widgets/{textWidget}', [TextWidgetController::class, 'show']);
|
||||
Route::put('/text-widgets/{textWidget}', [TextWidgetController::class, 'update']);
|
||||
Route::delete('/text-widgets/{textWidget}', [TextWidgetController::class, 'destroy']);
|
||||
|
||||
Route::post('/notifications/preview-site-built', [NotificationController::class, 'sendPreviewSiteBuilt']);
|
||||
});
|
||||
|
|
|
|||
7
routes/channels.php
Normal file
7
routes/channels.php
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
|
||||
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
|
||||
return (int) $user->id === (int) $id;
|
||||
});
|
||||
|
|
@ -32,4 +32,20 @@ Route::middleware(['auth'])->group(function () {
|
|||
),
|
||||
)
|
||||
->name('two-factor.show');
|
||||
|
||||
Route::get('/test-reverb', function () {
|
||||
event(new App\Events\TestEvent('Hello from Laravel at ' . now()));
|
||||
|
||||
return 'Event fired! Check your browser console.';
|
||||
})->name('test.reverb');
|
||||
|
||||
Route::get('/test-reverb-page', function () {
|
||||
return view('test-reverb');
|
||||
})->name('test.reverb.page');
|
||||
|
||||
Route::get('/test-preview-site-built', function () {
|
||||
event(new App\Events\PreviewSiteBuilt('Preview site is built at ' . now(), 'success'));
|
||||
|
||||
return 'Preview site built notification sent! Check your Filament admin panel for the toast notification.';
|
||||
})->name('test.preview.site.built');
|
||||
});
|
||||
|
|
|
|||
77
tests/Feature/NotificationControllerTest.php
Normal file
77
tests/Feature/NotificationControllerTest.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
use App\Events\PreviewSiteBuilt;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
use function Pest\Laravel\postJson;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
test('send preview site built notification requires authentication', function () {
|
||||
postJson('/api/notifications/preview-site-built', [
|
||||
'message' => 'Test notification',
|
||||
])->assertUnauthorized();
|
||||
});
|
||||
|
||||
test('send preview site built notification requires admin permissions', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
actingAs($user)
|
||||
->postJson('/api/notifications/preview-site-built', [
|
||||
'message' => 'Test notification',
|
||||
])
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('send preview site built notification validates message requirement', function () {
|
||||
$adminEmail = config('app.admin_email');
|
||||
$admin = User::factory()->create(['email' => $adminEmail]);
|
||||
|
||||
actingAs($admin)
|
||||
->postJson('/api/notifications/preview-site-built', [])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['message']);
|
||||
});
|
||||
|
||||
test('send preview site built notification validates message length', function () {
|
||||
$adminEmail = config('app.admin_email');
|
||||
$admin = User::factory()->create(['email' => $adminEmail]);
|
||||
|
||||
actingAs($admin)
|
||||
->postJson('/api/notifications/preview-site-built', [
|
||||
'message' => str_repeat('a', 256), // 256 characters, over the 255 limit
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['message']);
|
||||
});
|
||||
|
||||
test('send preview site built notification dispatches event successfully', function () {
|
||||
Event::fake();
|
||||
|
||||
$adminEmail = config('app.admin_email');
|
||||
$admin = User::factory()->create(['email' => $adminEmail]);
|
||||
$message = 'Test preview site notification';
|
||||
|
||||
actingAs($admin)
|
||||
->postJson('/api/notifications/preview-site-built', [
|
||||
'message' => $message,
|
||||
])
|
||||
->assertSuccessful()
|
||||
->assertJsonStructure([
|
||||
'success',
|
||||
'message',
|
||||
'data' => [
|
||||
'notification_message',
|
||||
],
|
||||
])
|
||||
->assertJsonPath('success', true)
|
||||
->assertJsonPath('data.notification_message', $message);
|
||||
|
||||
Event::assertDispatched(PreviewSiteBuilt::class, function ($event) use ($message) {
|
||||
return $event->message === $message
|
||||
&& $event->type === 'success';
|
||||
});
|
||||
});
|
||||
23
tests/Feature/PreviewSiteBuiltEventTest.php
Normal file
23
tests/Feature/PreviewSiteBuiltEventTest.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
use App\Events\PreviewSiteBuilt;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
it('can broadcast preview site built event', function () {
|
||||
Event::fake();
|
||||
|
||||
PreviewSiteBuilt::dispatch('Test preview site is built', 'success');
|
||||
|
||||
Event::assertDispatched(PreviewSiteBuilt::class, function ($event) {
|
||||
return $event->message === 'Test preview site is built'
|
||||
&& $event->type === 'success';
|
||||
});
|
||||
});
|
||||
|
||||
it('has correct broadcast channel and event name', function () {
|
||||
$event = new PreviewSiteBuilt('Test message', 'success');
|
||||
|
||||
expect($event->broadcastOn())->toHaveCount(1)
|
||||
->and($event->broadcastOn()[0]->name)->toBe('filament-notifications')
|
||||
->and($event->broadcastAs())->toBe('preview-site.built');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue