File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/BaseNotificationController.php
<?php
namespace App\Http\Controllers\Refactor;
use App\Http\Controllers\Controller;
use App\Model\AppNotification;
use App\Notifications\AppNotification as Event;
use App\Services\Notifications\Notifier;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Shared CRUD/inbox endpoints for both admin and customer notification
* surfaces. Subclasses provide:
* - inboxQuery(): the role-scoped Eloquent builder (direct rows for that
* user + any shared rows visible to that audience)
* - actor(): the Admin or Customer model used for read_by_* / dismissed_by_*
* audit columns
* - viewPath(): namespace prefix for the index/_list/dropdown blade files
*
* Authorization is enforced via NotificationPolicy at every per-row endpoint.
*/
abstract class BaseNotificationController extends Controller
{
use AuthorizesRequests;
public function __construct(protected Notifier $notifier) {}
abstract protected function inboxQuery(): Builder;
abstract protected function actor(): Model;
abstract protected function viewPath(): string;
abstract protected function routePrefix(): string;
public function index(Request $request)
{
$base = $this->inboxQuery();
$stats = [
'unread' => (clone $base)->whereNull('dismissed_at')->whereNull('read_at')->count(),
'dismissed' => (clone $base)->whereNotNull('dismissed_at')->count(),
'total' => $base->count(),
];
return view($this->viewPath() . '.index', [
'stats' => $stats,
'route_prefix' => $this->routePrefix(),
]);
}
public function listing(Request $request)
{
$q = $this->inboxQuery();
// Default tab = active (not dismissed). 'all' shows dismissed too.
$tab = $request->input('tab', 'active');
match ($tab) {
'active' => $q->whereNull('dismissed_at'),
'unread' => $q->whereNull('dismissed_at')->whereNull('read_at'),
'action' => $q->whereNull('dismissed_at')->where('category', Event::CATEGORY_ACTION),
'alert' => $q->whereNull('dismissed_at')->where('category', Event::CATEGORY_ALERT),
'dismissed' => $q->whereNotNull('dismissed_at'),
'all' => null,
default => throw new \LogicException("Unknown notifications tab: {$tab}"),
};
if ($kw = trim((string) $request->input('keyword', ''))) {
$like = '%' . $kw . '%';
$q->where(function ($qq) use ($kw, $like) {
$qq->whereJsonContains('data->title', $kw)
->orWhere('data', 'like', $like);
});
}
if ($sev = $request->input('severity')) {
$q->where('severity', $sev);
}
$items = $q->paginate($request->input('per_page', 20));
return view($this->viewPath() . '._list', [
'items' => $items,
'route_prefix' => $this->routePrefix(),
]);
}
public function dropdown(Request $request)
{
$items = $this->inboxQuery()
->whereNull('dismissed_at')
->whereNull('read_at')
->limit(8)
->get();
return view('refactor.components.notifications._dropdown_panel', [
'items' => $items,
'route_prefix' => $this->routePrefix(),
]);
}
public function badge(): JsonResponse
{
return response()->json([
'unread' => $this->inboxQuery()
->whereNull('dismissed_at')
->whereNull('read_at')
->count(),
]);
}
public function markRead(string $id): JsonResponse
{
$n = $this->findOrFail($id);
$this->authorize('act', $n);
$n->markAsRead($this->actor());
return response()->json(['ok' => true]);
}
public function markUnread(string $id): JsonResponse
{
$n = $this->findOrFail($id);
$this->authorize('act', $n);
$n->markAsUnread();
return response()->json(['ok' => true]);
}
public function dismiss(string $id): JsonResponse
{
$n = $this->findOrFail($id);
$this->authorize('act', $n);
$n->dismiss($this->actor());
return response()->json(['ok' => true]);
}
public function markAllRead(): JsonResponse
{
$now = now();
$actor = $this->actor();
$marked = $this->inboxQuery()
->whereNull('dismissed_at')
->whereNull('read_at')
->update([
'read_at' => $now,
'read_by_type' => $actor->getMorphClass(),
'read_by_id' => $actor->getKey(),
]);
return response()->json(['ok' => true, 'marked' => $marked]);
}
/**
* Resolve a notification by id and assert it's in this actor's inbox
* (defense-in-depth on top of the policy check, so we never touch a row
* the user can't see).
*/
protected function findOrFail(string $id): AppNotification
{
return $this->inboxQuery()->whereKey($id)->firstOrFail();
}
}