# Laravel Reverb Integration Guide

## Overview

This document outlines the integration of Laravel Reverb for real-time features in MVNexus, including ticket updates, notifications, and live collaboration.

## What is Laravel Reverb?

Laravel Reverb is a first-party WebSocket server for Laravel applications, providing:
- **Real-time Broadcasting**: Push updates to connected clients instantly
- **Presence Channels**: Track who's online and viewing tickets
- **Private Channels**: Secure, authenticated real-time communication
- **Horizontal Scaling**: Redis-backed for multi-server deployments

## Installation & Setup

### 1. Install Reverb

```bash
# Install Laravel Reverb
composer require laravel/reverb

# Publish configuration
php artisan reverb:install

# Start Reverb server
php artisan reverb:start
```

### 2. Configure `.env`

```.env
BROADCAST_DRIVER=reverb

REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http

# For production
REVERB_ALLOWED_ORIGINS=mvhelpdesk.local,https://app.mvhelpdesk.com
```

### 3. Frontend Setup

```bash
# Install Laravel Echo and Pusher JS
npm install --save laravel-echo pusher-js
```

## Backend Implementation

### 1. Broadcast Events

#### Ticket Updated Event

```php
<?php

namespace App\Modules\Ticketing\Events;

use App\Modules\Ticketing\Models\Ticket;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class TicketUpdated implements ShouldBroadcast
{
    use InteractsWithSockets, SerializesModels;

    public function __construct(public Ticket $ticket)
    {
    }

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("department.{$this->ticket->department_id}.tickets"),
            new PrivateChannel("ticket.{$this->ticket->id}"),
        ];
    }

    public function broadcastAs(): string
    {
        return 'ticket.updated';
    }

    public function broadcastWith(): array
    {
        return [
            'id' => $this->ticket->id,
            'subject' => $this->ticket->subject,
            'status' => $this->ticket->status->name,
            'priority' => $this->ticket->priority->name,
            'updated_at' => $this->ticket->updated_at->toIso8601String(),
        ];
    }
}
```

#### Reply Added Event

```php
<?php

namespace App\Modules\Replies\Events;

use App\Modules\Replies\Models\Reply;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ReplyAdded implements ShouldBroadcast
{
    public function __construct(public Reply $reply)
    {
    }

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("ticket.{$this->reply->ticket_id}"),
        ];
    }

    public function broadcastAs(): string
    {
        return 'reply.added';
    }

    public function broadcastWith(): array
    {
        return [
            'id' => $this->reply->id,
            'content' => $this->reply->content,
            'user' => [
                'id' => $this->reply->user->id,
                'name' => $this->reply->user->name,
                'avatar' => $this->reply->user->avatar_url,
            ],
            'created_at' => $this->reply->created_at->toIso8601String(),
        ];
    }
}
```

#### Notification Event

```php
<?php

namespace App\Modules\Notifications\Events;

use App\Modules\Notifications\Models\Notification;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class NotificationSent implements ShouldBroadcast
{
    public function __construct(public Notification $notification)
    {
    }

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("user.{$this->notification->user_id}"),
        ];
    }

    public function broadcastAs(): string
    {
        return 'notification.received';
    }

    public function broadcastWith(): array
    {
        return [
            'id' => $this->notification->id,
            'type' => $this->notification->type,
            'title' => $this->notification->title,
            'message' => $this->notification->message,
            'data' => $this->notification->data,
            'created_at' => $this->notification->created_at->toIso8601String(),
        ];
    }
}
```

### 2. Broadcasting Authorization

#### `routes/channels.php`

```php
<?php

use App\Modules\Identity\Models\User;
use App\Modules\Ticketing\Models\Ticket;
use Illuminate\Support\Facades\Broadcast;

// User's personal channel
Broadcast::channel('user.{userId}', function (User $user, string $userId) {
    return $user->id === $userId;
});

// Ticket detail channel (authorized users only)
Broadcast::channel('ticket.{ticketId}', function (User $user, string $ticketId) {
    $ticket = Ticket::findOrFail($ticketId);
    return $user->can('view', $ticket) ? ['id' => $user->id, 'name' => $user->name] : null;
});

// Department tickets channel
Broadcast::channel('department.{departmentId}.tickets', function (User $user, string $departmentId) {
    return $user->departments()->where('department_id', $departmentId)->exists()
        ? ['id' => $user->id, 'name' => $user->name]
        : null;
});

// Presence channel for online users
Broadcast::channel('online', function (User $user) {
    return ['id' => $user->id, 'name' => $user->name, 'avatar' => $user->avatar_url];
});
```

### 3. Dispatching Events

#### In TicketService

```php
<?php

namespace App\Modules\Ticketing\Services;

use App\Modules\Ticketing\Events\TicketUpdated;

class TicketService
{
    public function updateTicket(Ticket $ticket, TicketUpdateDTO $dto): Ticket
    {
        $ticket = $this->repository->update($ticket->id, $dto->toArray());
        
        // Broadcast update
        broadcast(new TicketUpdated($ticket))->toOthers();
        
        // Log audit
        $this->auditService->log('ticket.updated', $ticket);
        
        return $ticket;
    }
}
```

#### In ReplyService

```php
<?php

namespace App\Modules\Replies\Services;

use App\Modules\Replies\Events\ReplyAdded;

class ReplyService
{
    public function createReply(ReplyCreateDTO $dto): Reply
    {
        $reply = $this->repository->create($dto->toArray());
        
        // Broadcast new reply
        broadcast(new ReplyAdded($reply))->toOthers();
        
        // Send notifications
        $this->notificationService->notifyTicketParticipants($reply->ticket_id, $reply);
        
        return $reply;
    }
}
```

## Frontend Implementation

### 1. Echo Configuration

#### `resources/js/src/shared/utils/echo.ts`

```typescript
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

declare global {
  interface Window {
    Pusher: typeof Pusher;
    Echo: Echo;
  }
}

window.Pusher = Pusher;

export const initializeEcho = (token: string): Echo => {
  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'],
    authEndpoint: '/api/broadcasting/auth',
    auth: {
      headers: {
        Authorization: `Bearer ${token}`,
        Accept: 'application/json',
      },
    },
  });

  return window.Echo;
};

export const disconnectEcho = (): void => {
  if (window.Echo) {
    window.Echo.disconnect();
  }
};
```

### 2. Real-time Hook

#### `resources/js/src/shared/hooks/useRealtime.ts`

```typescript
import { useEffect } from 'react';
import { useAuthStore } from '@/features/auth/store/authStore';
import { initializeEcho, disconnectEcho } from '@/shared/utils/echo';

export const useRealtime = () => {
  const token = useAuthStore((state) => state.token);

  useEffect(() => {
    if (!token) return;

    const echo = initializeEcho(token);

    return () => {
      disconnectEcho();
    };
  }, [token]);
};
```

### 3. Listen to Ticket Updates

#### `resources/js/src/features/tickets/hooks/useTicketSubscription.ts`

```typescript
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';

export const useTicketSubscription = (ticketId: string) => {
  const queryClient = useQueryClient();

  useEffect(() => {
    if (!window.Echo || !ticketId) return;

    const channel = window.Echo.private(`ticket.${ticketId}`);

    // Listen for ticket updates
    channel.listen('.ticket.updated', (data: any) => {
      // Invalidate and refetch ticket
      queryClient.invalidateQueries({ queryKey: ['ticket', ticketId] });
      
      toast.success('Ticket updated');
    });

    // Listen for new replies
    channel.listen('.reply.added', (data: any) => {
      // Invalidate replies query
      queryClient.invalidateQueries({ queryKey: ['ticket', ticketId, 'replies'] });
      
      toast(`New reply from ${data.user.name}`);
    });

    return () => {
      channel.stopListening('.ticket.updated');
      channel.stopListening('.reply.added');
      window.Echo.leave(`ticket.${ticketId}`);
    };
  }, [ticketId, queryClient]);
};
```

### 4. Presence Channel (Who's Online)

#### `resources/js/src/features/tickets/hooks/useTicketPresence.ts`

```typescript
import { useState, useEffect } from 'react';

interface OnlineUser {
  id: string;
  name: string;
  avatar?: string;
}

export const useTicketPresence = (ticketId: string) => {
  const [viewers, setViewers] = useState<OnlineUser[]>([]);

  useEffect(() => {
    if (!window.Echo || !ticketId) return;

    const channel = window.Echo.join(`ticket.${ticketId}`);

    // User joined
    channel.here((users: OnlineUser[]) => {
      setViewers(users);
    });

    // Someone joined
    channel.joining((user: OnlineUser) => {
      setViewers((prev) => [...prev, user]);
    });

    // Someone left
    channel.leaving((user: OnlineUser) => {
      setViewers((prev) => prev.filter((u) => u.id !== user.id));
    });

    return () => {
      window.Echo.leave(`ticket.${ticketId}`);
    };
  }, [ticketId]);

  return viewers;
};
```

### 5. Notifications

#### `resources/js/src/features/notifications/hooks/useNotifications.ts`

```typescript
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { useAuthStore } from '@/features/auth/store/authStore';
import toast from 'react-hot-toast';

export const useNotifications = () => {
  const user = useAuthStore((state) => state.user);
  const queryClient = useQueryClient();

  useEffect(() => {
    if (!window.Echo || !user) return;

    const channel = window.Echo.private(`user.${user.id}`);

    channel.listen('.notification.received', (data: any) => {
      // Show toast notification
      toast(data.message, {
        icon: data.type === 'success' ? '✅' : '📢',
        duration: 5000,
      });

      // Refresh notifications count
      queryClient.invalidateQueries({ queryKey: ['notifications', 'unread'] });
    });

    return () => {
      channel.stopListening('.notification.received');
      window.Echo.leave(`user.${user.id}`);
    };
  }, [user, queryClient]);
};
```

### 6. Usage in Components

#### Ticket Detail Page

```typescript
import React from 'react';
import { useParams } from 'react-router-dom';
import { useTicketSubscription } from '../hooks/useTicketSubscription';
import { useTicketPresence } from '../hooks/useTicketPresence';

const TicketDetailPage: React.FC = () => {
  const { id } = useParams<{ id: string }>();
  
  // Subscribe to real-time updates
  useTicketSubscription(id!);
  
  // Track who's viewing
  const viewers = useTicketPresence(id!);

  return (
    <div>
      {/* Show online viewers */}
      {viewers.length > 0 && (
        <div>
          <strong>Viewing now:</strong>{' '}
          {viewers.map((v) => v.name).join(', ')}
        </div>
      )}
      
      {/* Rest of component */}
    </div>
  );
};
```

#### App Layout (Global Notifications)

```typescript
import React, { useEffect } from 'react';
import { useRealtime } from '@/shared/hooks/useRealtime';
import { useNotifications } from '@/features/notifications/hooks/useNotifications';

export const AppLayout: React.FC<{ children }> = ({ children }) => {
  // Initialize Echo connection
  useRealtime();
  
  // Listen for notifications
  useNotifications();

  return <div>{children}</div>;
};
```

## Environment Variables

Add to `.env` and `.env.example`:

```bash
# Laravel Reverb
BROADCAST_DRIVER=reverb
REVERB_APP_ID=
REVERB_APP_KEY=
REVERB_APP_SECRET=
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http
REVERB_ALLOWED_ORIGINS=*
```

Frontend (`.env`):

```bash
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
```

## Production Deployment

### Using Supervisor

```ini
[program:reverb]
command=php /var/www/mvhelpdesk/artisan reverb:start
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/mvhelpdesk/storage/logs/reverb.log
```

### Nginx Configuration

```nginx
location /app {
    proxy_pass http://localhost:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
}
```

## Testing Real-time Features

```php
<?php

test('ticket update broadcasts event', function () {
    Event::fake([TicketUpdated::class]);
    
    $ticket = Ticket::factory()->create();
    
    // Update ticket
    $this->ticketService->updateTicket($ticket, new TicketUpdateDTO([
        'subject' => 'Updated Subject',
    ]));
    
    // Assert event was broadcast
    Event::assertDispatched(TicketUpdated::class, function ($event) use ($ticket) {
        return $event->ticket->id === $ticket->id;
    });
});
```

## Summary

With Laravel Reverb integrated:
- ✅ Real-time ticket updates
- ✅ Instant notifications
- ✅ Live reply feed
- ✅ Presence tracking (who's viewing tickets)
- ✅ Department-wide broadcasts
- ✅ Secure, authenticated channels
- ✅ Horizontally scalable

---

**Next**: See `docs/DEPLOYMENT.md` for production deployment guidelines.
