From bebefc5c9510dcb7e710d843f3eea6c309d438a0 Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Tue, 12 May 2026 13:19:06 -0300 Subject: [PATCH 1/7] adicionando notification service --- app/Services/NotificationService.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/Services/NotificationService.php diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php new file mode 100644 index 0000000..e69de29 From 69a052f1f9fb72933507561ee711a1b8a75f7dca Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Tue, 12 May 2026 16:48:31 -0300 Subject: [PATCH 2/7] =?UTF-8?q?altera=C3=A7=C3=A3o=20no=20middleware=20do?= =?UTF-8?q?=20inertia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Middleware/HandleInertiaRequests.php | 14 +++- app/Services/NotificationService.php | 69 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index f514047..0c61a6c 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -39,16 +39,24 @@ public function share(Request $request): array 'currentRoute' => Route::currentRouteName(), ], 'notifications' => function () use ($request) { - if (!$request->user()) return []; - + if (!$request->user()) { + return []; + } return $request->user() - ->unreadNotifications + ->unreadNotifications() + ->latest() + ->limit(5) + ->get() ->map(fn ($notification) => [ 'id' => $notification->id, 'data' => $notification->data, 'created_at' => $notification->created_at, ]); }, + 'allUnreadCount' => + $request->user() + ->unreadNotifications() + ->count(), ]; } } diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index e69de29..e00686f 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -0,0 +1,69 @@ +notifications()->latest(); + + if (isset($filters['read'])) { + if ($filters['read']) { + $query->whereNotNull('read_at'); + } else { + $query->whereNull('read_at'); + } + } + + if (!empty($filters['type'])) { + $query->where('type', $filters['type']); + } + + /** @var LengthAwarePaginator $notifications */ + $notifications = $query->paginate($pagination); + + return [ + 'userNotifications' => $notifications->items(), + 'page' => $notifications->currentPage(), + 'pageCount' => $notifications->lastPage(), + 'total' => $notifications->total(), + 'unreadNotificationsCount' => $user->unreadNotifications()->count(), + ]; + } + + public function getUnreadCount(User $user): int + { + return $user->unreadNotifications()->count(); + } + + public function markAsRead(User $user, string $notificationId): bool + { + /** @var DatabaseNotification|null $notification */ + $notification = $user->notifications() + ->where('id', $notificationId) + ->first(); + + if (!$notification) { + return false; + } + + if (!$notification->read_at) { + $notification->markAsRead(); + } + + return true; + } + + public function markAllAsRead(User $user): void + { + $user->unreadNotifications->markAsRead(); + } +} \ No newline at end of file From dca17d68fa4bd088b65ffb9da0bfaca9f1eeb832 Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Wed, 13 May 2026 11:15:25 -0300 Subject: [PATCH 3/7] refatorando notification service --- app/Services/NotificationService.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index e00686f..dfaedd3 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -4,30 +4,26 @@ use App\Models\User; use Illuminate\Notifications\DatabaseNotification; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; class NoticeService { public function getUserNotifications( User $user, array $filters = [], - ?int $pagination = 5 + int $pagination = 5 ): array { $query = $user->notifications()->latest(); if (isset($filters['read'])) { - if ($filters['read']) { - $query->whereNotNull('read_at'); - } else { - $query->whereNull('read_at'); - } + $filters['read'] + ? $query->whereNotNull('read_at') + : $query->whereNull('read_at'); } if (!empty($filters['type'])) { $query->where('type', $filters['type']); } - /** @var LengthAwarePaginator $notifications */ $notifications = $query->paginate($pagination); return [ @@ -46,10 +42,7 @@ public function getUnreadCount(User $user): int public function markAsRead(User $user, string $notificationId): bool { - /** @var DatabaseNotification|null $notification */ - $notification = $user->notifications() - ->where('id', $notificationId) - ->first(); + $notification = $this->findNotification($user, $notificationId); if (!$notification) { return false; @@ -66,4 +59,13 @@ public function markAllAsRead(User $user): void { $user->unreadNotifications->markAsRead(); } + + private function findNotification( + User $user, + string $notificationId + ): ?DatabaseNotification { + return $user->notifications() + ->where('id', $notificationId) + ->first(); + } } \ No newline at end of file From 177bd84e636391d17df88a9cbcbf1dd80b375bf9 Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Wed, 13 May 2026 11:31:06 -0300 Subject: [PATCH 4/7] =?UTF-8?q?notifica=C3=A7=C3=B5es=20via=20inertia=20fe?= =?UTF-8?q?itas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/NotificationController.php | 78 +++++++++++++++++++ app/Services/NotificationService.php | 2 +- routes/web.php | 7 ++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/NotificationController.php diff --git a/app/Http/Controllers/NotificationController.php b/app/Http/Controllers/NotificationController.php new file mode 100644 index 0000000..fffa3fe --- /dev/null +++ b/app/Http/Controllers/NotificationController.php @@ -0,0 +1,78 @@ +service->getUserNotifications( + user: $request->user(), + filters: [ + 'read' => $request->has('read') + ? $request->boolean('read') + : null, + 'type' => $request->input('type'), + ], + pagination: 5 + ); + + return Inertia::render('Notifications/Index', [ + 'notifications' => $notifications, + ]); + } + + public function unreadCount(Request $request): array + { + return [ + 'count' => $this->service->getUnreadCount( + $request->user() + ), + ]; + } + + public function markAsRead( + Request $request, + string $id + ): RedirectResponse { + $success = $this->service->markAsRead( + $request->user(), + $id + ); + + if (!$success) { + return back()->with( + 'error', + 'Notification not found.' + ); + } + + return back()->with( + 'success', + 'Notification marked as read.' + ); + } + + public function markAllAsRead( + Request $request + ): RedirectResponse { + $this->service->markAllAsRead( + $request->user() + ); + + return back()->with( + 'success', + 'All notifications marked as read.' + ); + } +} \ No newline at end of file diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index dfaedd3..9f9b175 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -5,7 +5,7 @@ use App\Models\User; use Illuminate\Notifications\DatabaseNotification; -class NoticeService +class NotificationService { public function getUserNotifications( User $user, diff --git a/routes/web.php b/routes/web.php index ab6440a..e5c1392 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,6 +4,7 @@ use App\Http\Controllers\GroupController; use App\Http\Controllers\ProfileController; use App\Http\Controllers\NoticeController; +use App\Http\Controllers\NotificationController; use App\Http\Controllers\OpeningController; use App\Http\Controllers\ProjectController; use App\Models\User; @@ -69,6 +70,12 @@ Route::get('editais/{notice}/projetos/{project}', [ProjectController::class, 'projectDetail']) ->scopeBindings() ->name('notices.projects.show'); + Route::prefix('notificacoes')->group(function () { + Route::get('/', [NotificationController::class, 'index'])->name('notifications.index'); + Route::get('/nao-lidas', [NotificationController::class, 'unreadCount'])->name('notifications.unread-count'); + Route::patch('/{id}/ler', [NotificationController::class, 'markAsRead'])->name('notifications.mark-read'); + Route::patch('/ler-todas', [NotificationController::class, 'markAllAsRead'])->name('notifications.mark-all-read'); + }); }); Route::middleware('auth')->group(function () { From a886824873cbd4d604e951bc193f906f4d83680e Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Wed, 13 May 2026 11:50:53 -0300 Subject: [PATCH 5/7] =?UTF-8?q?removendo=20pagina=20de=20notifica=C3=A7?= =?UTF-8?q?=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/NotificationController.php | 7 +++---- app/Http/Middleware/HandleInertiaRequests.php | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/NotificationController.php b/app/Http/Controllers/NotificationController.php index fffa3fe..c5e399a 100644 --- a/app/Http/Controllers/NotificationController.php +++ b/app/Http/Controllers/NotificationController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Services\NotificationService; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Inertia\Inertia; use Inertia\Response; @@ -14,7 +15,7 @@ public function __construct( private readonly NotificationService $service ) {} - public function index(Request $request): Response + public function index(Request $request): JsonResponse { $notifications = $this->service->getUserNotifications( user: $request->user(), @@ -27,9 +28,7 @@ public function index(Request $request): Response pagination: 5 ); - return Inertia::render('Notifications/Index', [ - 'notifications' => $notifications, - ]); + return response()->json($notifications); } public function unreadCount(Request $request): array diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 0c61a6c..b975d64 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -53,10 +53,14 @@ public function share(Request $request): array 'created_at' => $notification->created_at, ]); }, - 'allUnreadCount' => - $request->user() + 'allUnreadCount' => function () use ($request) { + if (!$request->user()) { + return 0; + } + return $request->user() ->unreadNotifications() - ->count(), + ->count(); + }, ]; } } From f63bb70fae91f88d7498817a1e0dc7e77f901fa2 Mon Sep 17 00:00:00 2001 From: "Gabriel M." <79814036+CaffeineIssues@users.noreply.github.com> Date: Wed, 13 May 2026 11:57:56 -0300 Subject: [PATCH 6/7] Update PHP version from 8.3 to 8.4 in lint workflow --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6dc9801..a473059 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: "8.3" + php-version: "8.4" - run: composer install --no-interaction --prefer-dist - run: ./vendor/bin/pint --test From b851cc7fd49b1fb57e55e465b362bbd63c190d76 Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Wed, 13 May 2026 13:33:35 -0300 Subject: [PATCH 7/7] testes unitarios --- tests/Feature/NotificationServiceTest.php | 231 ++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 tests/Feature/NotificationServiceTest.php diff --git a/tests/Feature/NotificationServiceTest.php b/tests/Feature/NotificationServiceTest.php new file mode 100644 index 0000000..aeab2bc --- /dev/null +++ b/tests/Feature/NotificationServiceTest.php @@ -0,0 +1,231 @@ +service = app(NotificationService::class); + } + + private function createNotification( + User $user, + array $attributes = [] + ): DatabaseNotification { + return DatabaseNotification::create(array_merge([ + 'id' => (string) Str::uuid(), + 'type' => 'App\Notifications\TestNotification', + 'notifiable_type' => User::class, + 'notifiable_id' => $user->id, + 'data' => ['message' => 'Test notification'], + 'read_at' => null, + ], $attributes)); + } + + #[Test] + public function test_it_gets_paginated_notifications(): void + { + $user = User::factory()->create(); + + foreach (range(1, 10) as $i) { + $this->createNotification($user); + } + + $result = $this->service->getUserNotifications( + user: $user, + pagination: 5 + ); + + $this->assertCount(5, $result['userNotifications']); + $this->assertEquals(1, $result['page']); + $this->assertEquals(2, $result['pageCount']); + $this->assertEquals(10, $result['total']); + $this->assertEquals(10, $result['unreadNotificationsCount']); + } + + #[Test] + public function test_it_filters_unread_notifications(): void + { + $user = User::factory()->create(); + + $this->createNotification($user, [ + 'read_at' => null, + ]); + + $this->createNotification($user, [ + 'read_at' => now(), + ]); + + $result = $this->service->getUserNotifications( + user: $user, + filters: ['read' => false] + ); + + $this->assertCount(1, $result['userNotifications']); + $this->assertNull($result['userNotifications'][0]->read_at); + } + + #[Test] + public function test_it_filters_read_notifications(): void + { + $user = User::factory()->create(); + + $this->createNotification($user, [ + 'read_at' => null, + ]); + + $this->createNotification($user, [ + 'read_at' => now(), + ]); + + $result = $this->service->getUserNotifications( + user: $user, + filters: ['read' => true] + ); + + $this->assertCount(1, $result['userNotifications']); + $this->assertNotNull($result['userNotifications'][0]->read_at); + } + + #[Test] + public function test_it_filters_notifications_by_type(): void + { + $user = User::factory()->create(); + + $this->createNotification($user, [ + 'type' => 'App\Notifications\OrderNotification', + ]); + + $this->createNotification($user, [ + 'type' => 'App\Notifications\MessageNotification', + ]); + + $result = $this->service->getUserNotifications( + user: $user, + filters: [ + 'type' => 'App\Notifications\OrderNotification', + ] + ); + + $this->assertCount(1, $result['userNotifications']); + + $this->assertEquals( + 'App\Notifications\OrderNotification', + $result['userNotifications'][0]->type + ); + } + + #[Test] + public function test_it_gets_unread_count(): void + { + $user = User::factory()->create(); + + foreach (range(1, 3) as $i) { + $this->createNotification($user, [ + 'read_at' => null, + ]); + } + + foreach (range(1, 2) as $i) { + $this->createNotification($user, [ + 'read_at' => now(), + ]); + } + + $count = $this->service->getUnreadCount($user); + + $this->assertEquals(3, $count); + } + + #[Test] + public function test_it_marks_notification_as_read(): void + { + $user = User::factory()->create(); + + $notification = $this->createNotification($user, [ + 'read_at' => null, + ]); + + $result = $this->service->markAsRead( + user: $user, + notificationId: $notification->id + ); + + $notification->refresh(); + + $this->assertTrue($result); + $this->assertNotNull($notification->read_at); + } + + #[Test] + public function test_it_returns_false_when_notification_does_not_exist(): void + { + $user = User::factory()->create(); + + $result = $this->service->markAsRead( + user: $user, + notificationId: (string) Str::uuid() + ); + + $this->assertFalse($result); + } + + #[Test] + public function test_it_marks_all_notifications_as_read(): void + { + $user = User::factory()->create(); + + foreach (range(1, 3) as $i) { + $this->createNotification($user, [ + 'read_at' => null, + ]); + } + + $this->service->markAllAsRead($user); + + $this->assertEquals( + 0, + $user->fresh()->unreadNotifications()->count() + ); + } + + #[Test] + public function test_it_does_not_change_already_read_notification(): void + { + $user = User::factory()->create(); + + $readAt = Carbon::now()->subDay(); + + $notification = $this->createNotification($user, [ + 'read_at' => $readAt, + ]); + + $this->service->markAsRead( + user: $user, + notificationId: $notification->id + ); + + $notification->refresh(); + + $this->assertEquals( + $readAt->timestamp, + $notification->read_at->timestamp + ); + } +}