diff --git a/app/Http/Controllers/FrontController.php b/app/Http/Controllers/FrontController.php index 8d6103f..8e2cbab 100644 --- a/app/Http/Controllers/FrontController.php +++ b/app/Http/Controllers/FrontController.php @@ -2,26 +2,43 @@ namespace App\Http\Controllers; -use App\Models\Tag; -use App\Models\Guest; -use App\Models\Article; -use App\Models\Category; use Illuminate\View\View; use App\View\Shared\HomeSeo; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\Blade; -use Illuminate\Support\Facades\Cache; +use App\Notifications\UptimeMonitor; use Illuminate\View\AnonymousComponent; -use App\Http\Requests\FrontSearchRequest; use GrahamCampbell\GitHub\Facades\GitHub; use App\DataObjects\Front\GithubRepositoryData; use App\Support\Http\Controllers\BaseController; -use App\Http\Requests\FrontSubscribeNotificationRequest; use Illuminate\Database\Eloquent\Collection as EloquentCollection; +use Illuminate\Support\Facades\{ + DB, + Cache, + Blade, + Notification, +}; + +use App\Models\{ + Tag, + User, + Guest, + Article, + Category, + UptimeWebhookCall, +}; + +use App\Http\Requests\{ + FrontSearchRequest, + FrontUptimeWebhookRequest, + FrontSubscribeNotificationRequest, +}; + class FrontController extends BaseController { + private const UPTIME_DOWN_STATUS = 0; + /** * Create a new controller instance. * @@ -43,7 +60,7 @@ public function subscribeNotification(FrontSubscribeNotificationRequest $request $guest = Guest::firstOrCreate(['endpoint' => $endpoint = $request->input('endpoint')]); $subscription = $guest->updatePushSubscription( - $request->input('endpoint'), + $endpoint, $request->input('public_key'), $request->input('auth_token'), $request->input('encoding'), @@ -54,6 +71,49 @@ public function subscribeNotification(FrontSubscribeNotificationRequest $request return response()->json([], 200); } + /** + * Register the logged-in user so that they can receive notifications. + * + * @param \App\Http\Requests\FrontSubscribeNotificationRequest $request + * + * @return \Illuminate\Http\JsonResponse + */ + public function addUserToBeNotifiedToUptimeMonitor(FrontSubscribeNotificationRequest $request): JsonResponse + { + /** @var \App\Models\User */ + $user = auth()->user(); + + DB::transaction(fn () => + $user->updateShouldBeNotified() + ->updatePushSubscription( + $request->input('endpoint'), + $request->input('public_key'), + $request->input('auth_token'), + $request->input('encoding'), + ) + ); + + return response()->json(status: JsonResponse::HTTP_CREATED); + } + + /** + * @param \App\Http\Requests\FrontUptimeWebhookRequest $request + * + * @return \Illuminate\Http\JsonResponse + */ + public function uptimeWebhook(FrontUptimeWebhookRequest $request): JsonResponse + { + UptimeWebhookCall::create($request->validated()); + + if ($request->input('heartbeat')['status'] === self::UPTIME_DOWN_STATUS) { + report($message = trim($request->input('msg'), '"')); + } + + Notification::send(User::onlyShouldBeNotified()->get(), new UptimeMonitor($message)); + + return response()->json(status: JsonResponse::HTTP_NO_CONTENT); + } + /** * Blog home page. * diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 9e86521..b2aee62 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,7 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - // + '/github-webhooks', + '/uptime/webhook', ]; } diff --git a/app/Http/Requests/FrontUptimeWebhookRequest.php b/app/Http/Requests/FrontUptimeWebhookRequest.php new file mode 100644 index 0000000..9fe5a32 --- /dev/null +++ b/app/Http/Requests/FrontUptimeWebhookRequest.php @@ -0,0 +1,22 @@ + + */ + public function rules(): array + { + return [ + 'heartbeat' => ['sometimes', 'nullable', 'required', 'array'], + 'monitor' => ['sometimes', 'nullable', 'required', 'array'], + 'msg' => ['required',], + ]; + } +} diff --git a/app/Models/UptimeWebhookCall.php b/app/Models/UptimeWebhookCall.php new file mode 100644 index 0000000..29509ac --- /dev/null +++ b/app/Models/UptimeWebhookCall.php @@ -0,0 +1,30 @@ + + */ + protected $fillable = [ + 'msg', + 'heartbeat', + 'monitor', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'msg' => 'array', + 'heartbeat' => 'array', + 'monitor' => 'array', + ]; +} diff --git a/app/Models/User.php b/app/Models/User.php index dc28274..5036b5e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,12 +4,14 @@ use Laravel\Sanctum\HasApiTokens; use Illuminate\Notifications\Notifiable; +use Illuminate\Database\Eloquent\Builder; +use NotificationChannels\WebPush\HasPushSubscriptions; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { - use HasApiTokens, HasFactory, Notifiable; + use HasApiTokens, HasFactory, Notifiable, HasPushSubscriptions; /** * The attributes that are mass assignable. @@ -20,6 +22,7 @@ class User extends Authenticatable 'name', 'email', 'password', + 'should_be_notified', ]; /** @@ -40,5 +43,21 @@ class User extends Authenticatable protected $casts = [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'should_be_notified' => 'boolean', ]; + + public function updateShouldBeNotified(bool $shouldBeNotified = true): static + { + $this->fill(['should_be_notified' => $shouldBeNotified])->save(); + + return $this; + } + + /** + * Scope a query to only include users who should be notified. + */ + public function scopeOnlyShouldBeNotified(Builder $query): Builder + { + return $query->where('should_be_notified', true); + } } diff --git a/app/Notifications/NewArticle.php b/app/Notifications/NewArticle.php index fa73efc..6795420 100644 --- a/app/Notifications/NewArticle.php +++ b/app/Notifications/NewArticle.php @@ -34,12 +34,6 @@ public function via($notifiable) return [WebPushChannel::class]; } - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ public function toWebPush($notifiable, $notification) { return (new WebPushMessage) diff --git a/app/Notifications/UptimeMonitor.php b/app/Notifications/UptimeMonitor.php new file mode 100644 index 0000000..581d508 --- /dev/null +++ b/app/Notifications/UptimeMonitor.php @@ -0,0 +1,40 @@ + + */ + public function via(object $notifiable): array + { + return [WebPushChannel::class]; + } + + public function toWebPush($notifiable, $notification) + { + return (new WebPushMessage) + ->title($this->message) + ->icon(mix('/images/favicons/favicon-192x192.png')) + ->body($this->message); + } +} diff --git a/database/migrations/2024_02_25_170912_add_should_be_notified_to_users_table.php b/database/migrations/2024_02_25_170912_add_should_be_notified_to_users_table.php new file mode 100644 index 0000000..fbc5471 --- /dev/null +++ b/database/migrations/2024_02_25_170912_add_should_be_notified_to_users_table.php @@ -0,0 +1,29 @@ +boolean('should_be_notified')->after('remember_token')->index()->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['should_be_notified']); + $table->dropColumn('should_be_notified'); + }); + } +}; diff --git a/database/migrations/2024_02_25_171245_create_uptime_webhook_calls_table.php b/database/migrations/2024_02_25_171245_create_uptime_webhook_calls_table.php new file mode 100644 index 0000000..f997204 --- /dev/null +++ b/database/migrations/2024_02_25_171245_create_uptime_webhook_calls_table.php @@ -0,0 +1,34 @@ +id(); + $table->json('msg'); + $table->json('heartbeat'); + $table->json('monitor'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('uptime_webhook_calls'); + } +}; diff --git a/resources/js/install-sw-workbox.js b/resources/js/install-sw-workbox.js index 5826943..dc5f95e 100644 --- a/resources/js/install-sw-workbox.js +++ b/resources/js/install-sw-workbox.js @@ -129,6 +129,41 @@ try { } } + async function addUserToUptimeNotifications(pushManager) { + const subscription = await pushManager.getSubscription() + + const key = subscription.getKey('p256dh') + const token = subscription.getKey('auth') + const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0] + + const data = { + endpoint: subscription.endpoint, + public_key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null, + auth_token: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null, + encoding: contentEncoding, + }; + + axios.post('/notifications/add-user-to-uptime-monitor', data, { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + } + }).then(function (response) { + Swal.fire({ + text: 'Você foi adicionado com sucesso para receber notificações do Health Check dos monitores do UPTIME', + icon: "success" + }); + }) + .catch(function (error) { + console.error(error.response); + + Swal.fire({ + text: JSON.stringify(error.response.data), + icon: "error" + }); + }); + } + const pushNotificationAccepted = () => window.localStorage.getItem('acceptPushNotifications'); (async () => { @@ -266,6 +301,9 @@ try { document.querySelector('.enable-notifications') .addEventListener('click', () => enableNotifications(registration.pushManager)); + + document.querySelector('.add-user-to-uptime-notifications') + .addEventListener('click', () => addUserToUptimeNotifications(registration.pushManager)); }); } else { diff --git a/resources/views/layouts/partials/_footer.blade.php b/resources/views/layouts/partials/_footer.blade.php index 88450d7..2b24059 100644 --- a/resources/views/layouts/partials/_footer.blade.php +++ b/resources/views/layouts/partials/_footer.blade.php @@ -10,7 +10,6 @@ RSS Feed | Sitemap | - @auth I'm AUTHENTICATED @endauth diff --git a/resources/views/pages/shared/_post-content.blade.php b/resources/views/pages/shared/_post-content.blade.php index 7ff818d..c9e74c8 100644 --- a/resources/views/pages/shared/_post-content.blade.php +++ b/resources/views/pages/shared/_post-content.blade.php @@ -93,3 +93,5 @@
+ +@auth @endauth diff --git a/routes/auth.php b/routes/auth.php index 495f7e3..a0b3732 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -10,7 +10,7 @@ | */ -Route::middleware('auth')->name('logged.')->group(function () { +Route::middleware('auth:sanctum')->name('logged.')->group(function () { Route::name('me.')->group(function () { Route::get('me/profile', fn() => auth()->user())->name('profile'); }); diff --git a/routes/web.php b/routes/web.php index 022a268..dc6b316 100644 --- a/routes/web.php +++ b/routes/web.php @@ -37,6 +37,11 @@ Route::get('/tags', 'tagsShow')->name('tags.show'); Route::get('/hi', 'aboutMe')->name('about-me'); + + Route::middleware('auth:sanctum')->name('logged.')->group(function () { + Route::post('/notifications/add-user-to-uptime-monitor', 'addUserToBeNotifiedToUptimeMonitor')->name('notifications.add-user-to-uptime-monitor'); + Route::post('/uptime/webhook', 'uptimeWebhook')->name('uptime.webhook'); + }); }); // Route::get('/benchmark', function () {