# Notification Handling in the API

This document outlines how notifications are managed and sent within the API, covering database structure, API endpoints, job processing, and push notification mechanisms.

## 1. Database Structure

Notifications are stored in the `notifications` table and are associated with users through a pivot table, `notification_user`.

### [`app/Models/Notification.php`](app/Models/Notification.php)

The [`Notification`](app/Models/Notification.php:8) model defines the core structure of a notification and its relationship with users.

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Notification extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'body',
        'type', // 'user' for specific users, 'broadcast' for all users
    ];

    /**
     * The users that belong to the notification.
     */
    public function users()
    {
        return $this->belongsToMany(User::class, 'notification_user')
                    ->using(\App\Models\NotificationUser::class)
                    ->withPivot('read_at', 'deleted_at') // Pivot attributes for read status and soft delete
                    ->withTimestamps();
    }
}
```

The `notification_user` pivot table includes:
*   `notification_id`: Foreign key to the `notifications` table.
*   `user_id`: Foreign key to the `users` table.
*   `read_at`: Timestamp indicating when a user read the notification (for both user-specific and broadcast notifications).
*   `deleted_at`: Timestamp indicating when a user soft-deleted the notification.
*   `created_at`, `updated_at`: Standard timestamps.

## 2. API Endpoints ([`routes/api.php`](routes/api.php))

The API exposes several endpoints for managing notifications, primarily handled by the [`NotificationController`](app/Http/Controllers/Api/NotificationController.php).

### User-facing Endpoints (Authenticated)

```php
Route::middleware('auth:sanctum')->group(function () {
    Route::get('notifications', [\App\Http\Controllers\Api\NotificationController::class, 'index']);
    Route::post('notifications/{notification}/read', [\App\Http\Controllers\Api\NotificationController::class, 'markAsRead']);
    Route::delete('notifications/{notification}', [\App\Http\Controllers\Api\NotificationController::class, 'destroy']);
    Route::get('notifications/unread-count', [\App\Http\Controllers\Api\NotificationController::class, 'unreadCount']);
});
```

### Admin-facing Endpoints (Authenticated with `admin` middleware)

```php
Route::middleware(['auth:sanctum', 'admin'])->prefix('admin')->group(function () {
    Route::post('notifications', [\App\Http\Controllers\Api\NotificationController::class, 'store']);
});
```

## 3. Notification Controller ([`app/Http/Controllers/Api/NotificationController.php`](app/Http/Controllers/Api/NotificationController.php))

This controller handles the logic for listing, storing, marking as read, deleting, and counting notifications.

```php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\NotificationResource;
use App\Jobs\SendNotificationToUsers;
use App\Models\Notification;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;

class NotificationController extends Controller
{
    /**
     * Display a paginated list of the user's notifications.
     */
    public function index(Request $request) { /* ... */ }

    /**
     * Store a new notification and attach it to users.
     * (Admin Only)
     */
    public function store(Request $request)
    {
        $this->authorize('create', Notification::class); // Policy check

        $validatedData = $request->validate([
            'title' => 'required|string|max:255',
            'body' => 'required|string',
            'type' => 'required|in:user,broadcast',
            'user_ids' => 'required_if:type,user|array',
            'user_ids.*' => 'exists:users,id',
        ]);

        $notification = Notification::create($validatedData);

        if ($validatedData['type'] === 'user' && !empty($validatedData['user_ids'])) {
            $notification->users()->attach($validatedData['user_ids']);
            // Dispatch job to send push notifications to specific users
            SendNotificationToUsers::dispatch($notification->id, $notification->type, $validatedData['user_ids']);
        } else if ($validatedData['type'] === 'broadcast') {
            // Dispatch job to send push notifications to all users
            SendNotificationToUsers::dispatch($notification->id, $notification->type);
        }

        return new NotificationResource($notification);
    }

    /**
     * Mark a notification as read for the authenticated user.
     */
    public function markAsRead(Notification $notification)
    {
        $user = Auth::user();
        $user->notifications()->updateOrCreate(
            ['notification_id' => $notification->id],
            ['read_at' => now()]
        );
        return response()->json(['message' => 'Notification marked as read.']);
    }

    /**
     * Mark a notification as deleted for the authenticated user (soft delete).
     */
    public function destroy(Notification $notification)
    {
        $user = Auth::user();
        $user->notifications()->updateOrCreate(
            ['notification_id' => $notification->id],
            ['deleted_at' => now()]
        );
        return response()->json(['message' => 'Notification has been removed.']);
    }

    /**
     * Get the count of unread notifications for the authenticated user.
     */
    public function unreadCount() { /* ... */ }
}
```

## 4. Jobs for Sending Notifications

Notifications are sent asynchronously using Laravel jobs, primarily for push notifications via FCM.

### [`app/Jobs/SendNotificationToUsers.php`](app/Jobs/SendNotificationToUsers.php)

This job is responsible for dispatching push notifications to users based on the notification type (user-specific or broadcast).

```php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Notification as AppNotification;
use App\Models\User;
use App\Notifications\SendPushNotification;
use Illuminate\Support\Facades\Log;

class SendNotificationToUsers implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $notificationId;
    protected $notificationType;
    protected $userIds;

    public function __construct(int $notificationId, string $notificationType, ?array $userIds = null)
    {
        $this->notificationId = $notificationId;
        $this->notificationType = $notificationType;
        $this->userIds = $userIds;
        $this->afterCommit();
    }

    public function handle(): void
    {
        $notification = AppNotification::find($this->notificationId);

        if (!$notification) {
            Log::error('SendNotificationToUsers Job: Notification not found.', ['notification_id' => $this->notificationId]);
            return;
        }

        $users = collect();
        
        if ($notification->type === 'broadcast') {
            $users = User::whereHas('pushTokens')->get(); // Get all users with push tokens
        } else {
            if (!empty($this->userIds)) {
                $users = $notification->users()->whereIn('users.id', $this->userIds)->whereHas('pushTokens')->get();
            } else {
                $users = $notification->users()->whereHas('pushTokens')->get();
            }
        }

        foreach ($users as $user) {
            try {
                $deviceTokens = $user->pushTokens()->pluck('token')->toArray();
                if (!empty($deviceTokens)) {
                    $user->notify(new SendPushNotification(
                        $notification->title,
                        $notification->body,
                        $notification->type
                    ));
                }
            } catch (\Exception $e) {
                Log::error('Job: Error sending push notification.', ['notification_id' => $notification->id, 'user_id' => $user->id, 'error' => $e->getMessage()]);
            }
        }
    }
}
```

### [`app/Jobs/SendNewProjectNotification.php`](app/Jobs/SendNewProjectNotification.php)

This specific job is dispatched when a new project is created, sending a notification to the project's associated user.

```php
<?php

namespace App\Jobs;

use App\Models\Project;
use App\Models\User;
use App\Notifications\NewProjectNotification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendNewProjectNotification implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $project;

    public function __construct(Project $project)
    {
        $this->project = $project;
    }

    public function handle()
    {
        $user = User::find($this->project->user_id);

        if ($user) {
            $user->notify(new NewProjectNotification($this->project));
        }
    }
}
```

## 5. Notification Classes

Laravel's notification system is used to define how different types of notifications are formatted and sent.

### [`app/Notifications/SendPushNotification.php`](app/Notifications/SendPushNotification.php)

This class defines the structure for generic push notifications sent via FCM.

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use NotificationChannels\Fcm\FcmChannel;
use NotificationChannels\Fcm\FcmMessage;
use NotificationChannels\Fcm\Resources\AndroidConfig;
use NotificationChannels\Fcm\Resources\AndroidMessagePriority;
use NotificationChannels\Fcm\Resources\AndroidNotification;
use NotificationChannels\Fcm\Resources\ApnsConfig;
use NotificationChannels\Fcm\Resources\ApnsFcmOptions;
use NotificationChannels\Fcm\Resources\Notification as FcmNotification;

class SendPushNotification extends Notification
{
    use Queueable;

    protected $title;
    protected $body;
    protected $notificationType;

    public function __construct(string $title, string $body, string $notificationType = 'user')
    {
        $this->title = $title;
        $this->body = $body;
        $this->notificationType = $notificationType;
    }

    public function via($notifiable): array
    {
        return [FcmChannel::class];
    }

    public function toFcm($notifiable): FcmMessage
    {
        return FcmMessage::create()
            ->setNotification(
                FcmNotification::create()
                    ->setTitle($this->title)
                    ->setBody($this->body)
            )
            ->setData([
                'title' => $this->title,
                'body' => $this->body,
                'type' => $this->notificationType,
                'click_action' => 'FLUTTER_NOTIFICATION_CLICK',
                'timestamp' => now()->toISOString(),
            ])
            ->setApns( /* ... APNS configuration ... */ )
            ->setAndroid( /* ... Android configuration ... */ );
    }

    /**
     * Get the FCM device tokens for the notifiable.
     */
    public function routeNotificationForFcm($notifiable)
    {
        return $notifiable->pushTokens()->pluck('token')->toArray();
    }
}
```

### [`app/Notifications/NewProjectNotification.php`](app/Notifications/NewProjectNotification.php)

This class extends the base `Notification` and is specifically for new project assignments.

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use NotificationChannels\Fcm\FcmChannel;
use NotificationChannels\Fcm\FcmMessage;
use NotificationChannels\Fcm\Resources\AndroidConfig;
use NotificationChannels\Fcm\Resources\AndroidFcmOptions;
use NotificationChannels\Fcm\Resources\AndroidNotification;
use NotificationChannels\Fcm\Resources\ApnsConfig;
use NotificationChannels\Fcm\Resources\ApnsFcmOptions;
use NotificationChannels\Fcm\Resources\WebpushConfig;
use NotificationChannels\Fcm\Resources\WebpushFcmOptions;
use NotificationChannels\Fcm\Resources\Notification as FcmNotification;

class NewProjectNotification extends Notification implements ShouldQueue
{
    use Queueable;

    protected $project;

    public function __construct(Project $project)
    {
        $this->project = $project;
    }

    public function via($notifiable)
    {
        return [FcmChannel::class];
    }

    public function toFcm($notifiable)
    {
        return FcmMessage::create()
            ->setNotification(
                FcmNotification::create()
                    ->setTitle('New Project Assigned!')
                    ->setBody('A new project "' . $this->project->name . '" has been assigned to you.')
            )
            ->setData(['projectId' => $this->project->id])
            ->setAndroid( /* ... Android configuration ... */ )
            ->setApns( /* ... APNS configuration ... */ )
            ->setWebpush( /* ... Webpush configuration ... */ );
    }
}
```

## 6. Notification Observer ([`app/Observers/NotificationObserver.php`](app/Observers/NotificationObserver.php))

The `NotificationObserver` was previously used to dispatch notification jobs, but this responsibility has been moved to the `NotificationController` for direct dispatch.

```php
<?php

namespace App\Observers;

use App\Models\Notification as AppNotification;
use Illuminate\Support\Facades\Log;

class NotificationObserver
{
    /**
     * Handle the Notification "created" event.
     */
    public function created(AppNotification $notification)
    {
        // The job is now dispatched directly from the controller.
        // This observer no longer needs to dispatch the job.
        Log::info('NotificationObserver: Notification created, job dispatched by controller.', [
            'notification_id' => $notification->id,
            'type' => $notification->type
        ]);
    }
}