Browse Source

Send notification to multiple users

Jovan Jovanovic 3 years ago
parent
commit
e97ae55725

+ 33 - 9
app/Http/Controllers/Admin/UserController.php

@@ -6,6 +6,7 @@ use App\Classes\Pterodactyl;
 use App\Http\Controllers\Controller;
 use App\Models\User;
 use App\Notifications\DynamicNotification;
+use Spatie\QueryBuilder\QueryBuilder;
 use Exception;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
@@ -16,6 +17,7 @@ use Illuminate\Http\Response;
 use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\Notification;
 use Illuminate\Support\HtmlString;
 use Illuminate\Validation\Rule;
 use Illuminate\Validation\ValidationException;
@@ -53,6 +55,28 @@ class UserController extends Controller
         ]);
     }
 
+    /**
+     * Get a JSON response of users.
+     *
+     * @return \Illuminate\Support\Collection|\App\models\User
+     */
+    public function json(Request $request)
+    {
+        $users = QueryBuilder::for(User::query())->allowedFilters(['id', 'name', 'pterodactyl_id', 'email'])->paginate(25);
+
+        if ($request->query('user_id')) {
+            $user = User::query()->findOrFail($request->input('user_id'));
+            $user->avatarUrl = $user->getAvatar();
+
+            return $user;
+        }
+
+        return $users->map(function ($item) {
+            $item->avatarUrl = $item->getAvatar();
+
+            return $item;
+        });
+    }
     /**
      * Show the form for editing the specified resource.
      *
@@ -152,9 +176,7 @@ class UserController extends Controller
      */
     public function notifications(User $user)
     {
-        return view('admin.users.notifications')->with([
-            'user' => $user
-        ]);
+        return view('admin.users.notifications');
     }
 
     /**
@@ -165,11 +187,14 @@ class UserController extends Controller
      * @return RedirectResponse
      * @throws Exception
      */
-    public function notify(Request $request, User $user)
+    public function notify(Request $request)
     {
         $data = $request->validate([
             "via" => "required|min:1|array",
             "via.*" => "required|string|in:mail,database",
+            "all" => "required_without:users|boolean",
+            "users" => "required_without:all|min:1|array",
+            "users.*" => "exists:users,id",
             "title" => "required|string|min:1",
             "content" => "required|string|min:1"
         ]);
@@ -187,10 +212,10 @@ class UserController extends Controller
                 ->subject($data["title"])
                 ->line(new HtmlString($data["content"]));
         }
-        $user->notify(
-            new DynamicNotification($data["via"], $database, $mail)
-        );
-        return redirect()->route('admin.users.notifications', $user->id)->with('success', 'User notified!');
+        $all = $data["all"] ?? false;
+        $users = $all ? User::all() : User::whereIn("id", $data["users"])->get();
+        Notification::send($users, new DynamicNotification($data["via"], $database, $mail));
+        return redirect()->route('admin.users.notifications')->with('success', 'Notification sent!');
     }
 
     /**
@@ -228,7 +253,6 @@ class UserController extends Controller
                 <a data-content="Login as user" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.loginas', $user->id) . '" class="btn btn-sm btn-primary mr-1"><i class="fas fa-sign-in-alt"></i></a>
                 <a data-content="Show" data-toggle="popover" data-trigger="hover" data-placement="top"  href="' . route('admin.users.show', $user->id) . '" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-eye"></i></a>
                 <a data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top"  href="' . route('admin.users.edit', $user->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a>
-                <a data-content="Notifications" data-toggle="popover" data-trigger="hover" data-placement="top"  href="' . route('admin.users.notifications', $user->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-paper-plane"></i></a>
                 <form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.users.destroy', $user->id) . '">
                             ' . csrf_field() . '
                             ' . method_field("DELETE") . '

+ 10 - 11
app/Http/Controllers/Api/NotificationController.php

@@ -9,6 +9,7 @@ use App\Notifications\DynamicNotification;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Support\Facades\Notification;
 use Illuminate\Support\HtmlString;
 use Spatie\ValidationRules\Rules\Delimited;
 
@@ -56,34 +57,32 @@ class NotificationController extends Controller
      * @param int $userId
      * @return JsonResponse
      */
-    public function send(Request $request, int $userId)
+    public function send(Request $request)
     {
-        $discordUser = DiscordUser::find($userId);
-        $user = $discordUser ? $discordUser->user : User::findOrFail($userId);
-
         $data = $request->validate([
             "via" => ["required", new Delimited("in:mail,database")],
+            "all" => "required_without:users|boolean",
+            "users" => ["required_without:all", new Delimited("exists:users,id")],
             "title" => "required|string|min:1",
             "content" => "required|string|min:1"
         ]);
-        $via = explode(',', $data["via"]);
+        $via = explode(",", $data["via"]);
         $mail = null;
         $database = null;
-        if (in_array('database', $via)) {
+        if (in_array("database", $via)) {
             $database = [
                 "title" => $data["title"],
                 "content" => $data["content"]
             ];
         }
-        if (in_array('mail', $via)) {
+        if (in_array("mail", $via)) {
             $mail = (new MailMessage)
                 ->subject($data["title"])
                 ->line(new HtmlString($data["content"]));
         }
-        $user->notify(
-            new DynamicNotification($via, $database, $mail)
-        );
-
+        $all = $data["all"] ?? false;
+        $users = $all ? User::all() : User::whereIn("id", explode(",", $data["users"]))->get();
+        Notification::send($users, new DynamicNotification($via, $database, $mail));
         return response()->json(["message" => "Notification successfully sent."]);
     }
 

+ 1 - 0
composer.json

@@ -23,6 +23,7 @@
         "paypal/rest-api-sdk-php": "^1.14",
         "socialiteproviders/discord": "^4.1",
         "spatie/laravel-activitylog": "^3.16",
+        "spatie/laravel-query-builder": "^3.5",
         "spatie/laravel-validation-rules": "^3.0",
         "yajra/laravel-datatables-oracle": "~9.0"
     },

+ 71 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "eb7191a6b0476ec319915f6b98561af9",
+    "content-hash": "b3b61a46d5d4d6560d052cfda863d12c",
     "packages": [
         {
             "name": "asm89/stack-cors",
@@ -3460,6 +3460,76 @@
             ],
             "time": "2021-03-02T16:49:06+00:00"
         },
+        {
+            "name": "spatie/laravel-query-builder",
+            "version": "3.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/laravel-query-builder.git",
+                "reference": "4e5257be24139836dc092f618d7c73bcb1c00302"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/4e5257be24139836dc092f618d7c73bcb1c00302",
+                "reference": "4e5257be24139836dc092f618d7c73bcb1c00302",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/database": "^6.20.13|^7.30.4|^8.22.2",
+                "illuminate/http": "^6.20.13|7.30.4|^8.22.2",
+                "illuminate/support": "^6.20.13|7.30.4|^8.22.2",
+                "php": "^7.3|^8.0"
+            },
+            "require-dev": {
+                "ext-json": "*",
+                "laravel/legacy-factories": "^1.0.4",
+                "mockery/mockery": "^1.4",
+                "orchestra/testbench": "^4.9|^5.8|^6.3",
+                "phpunit/phpunit": "^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "Spatie\\QueryBuilder\\QueryBuilderServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\QueryBuilder\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Alex Vanderbist",
+                    "email": "alex@spatie.be",
+                    "homepage": "https://spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Easily build Eloquent queries from API requests",
+            "homepage": "https://github.com/spatie/laravel-query-builder",
+            "keywords": [
+                "laravel-query-builder",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/laravel-query-builder/issues",
+                "source": "https://github.com/spatie/laravel-query-builder"
+            },
+            "funding": [
+                {
+                    "url": "https://spatie.be/open-source/support-us",
+                    "type": "custom"
+                }
+            ],
+            "time": "2021-07-05T14:17:44+00:00"
+        },
         {
             "name": "spatie/laravel-validation-rules",
             "version": "3.0.0",

+ 2 - 0
resources/views/admin/users/index.blade.php

@@ -28,6 +28,8 @@
                 <div class="card-header">
                     <div class="d-flex justify-content-between">
                         <h5 class="card-title"><i class="fas fa-users mr-2"></i>Users</h5>
+                        <a href="{{route('admin.users.notifications')}}" class="btn btn-sm btn-primary"><i
+                                class="fas fa-paper-plane mr-1"></i>Notify</a>
                     </div>
                 </div>
 

+ 89 - 4
resources/views/admin/users/notifications.blade.php

@@ -1,5 +1,4 @@
 @extends('layouts.main')
-
 @section('content')
     <!-- CONTENT HEADER -->
     <section class="content-header">
@@ -13,7 +12,7 @@
                         <li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
                         <li class="breadcrumb-item"><a href="{{route('admin.users.index')}}">Users</a></li>
                         <li class="breadcrumb-item"><a class="text-muted"
-                                                       href="{{route('admin.users.notifications' , $user->id)}}">Notifications</a></li>
+                                                       href="{{route('admin.users.notifications')}}">Notifications</a></li>
                     </ol>
                 </div>
             </div>
@@ -29,10 +28,31 @@
                 <div class="col-lg-12">
                     <div class="card">
                         <div class="card-body">
-                            <form action="{{route('admin.users.notifications', $user->id)}}" method="POST">
+                            <form action="{{route('admin.users.notifications')}}" method="POST">
                                 @csrf
                                 @method('POST')
 
+                                <div class="form-group">
+                                    <label>Users</label><br>
+                                    <input id="all" name="all"
+                                           type="checkbox"
+                                           onchange="toggleClass('users-form', 'd-none')">
+                                    <label for="all">All</label>
+                                    <div id="users-form">
+                                        <select id="users" name="users[]" class="form-control" multiple></select>
+                                    </div>
+                                    @error('all')
+                                        <div class="invalid-feedback d-block">
+                                            {{$message}}
+                                        </div>
+                                    @enderror
+                                    @error('users')
+                                        <div class="invalid-feedback d-block">
+                                            {{$message}}
+                                        </div>
+                                    @enderror
+                                </div>
+
                                 <div class="form-group">
                                     <label>Send via</label><br>
                                     <input value="database" id="database" name="via[]"
@@ -103,7 +123,72 @@
                     [ 'view', [ 'undo', 'redo', 'fullscreen', 'codeview', 'help' ] ]
                 ]
             })
-        })
+
+            $('#users').select2({
+                ajax: {
+                    url: '/admin/users.json',
+                    dataType: 'json',
+                    delay: 250,
+
+                    data: function (params) {
+                        return {
+                            filter: { email: params.term },
+                            page: params.page,
+                        };
+                    },
+
+                    processResults: function (data, params) {
+                        return { results: data };
+                    },
+
+                    cache: true,
+                },
+                minimumInputLength: 2,
+                templateResult: function (data) {
+                    if (data.loading) return escapeHtml(data.text);
+                    const $container = $(
+                        "<div class='select2-result-repository clearfix' style='display:flex;'>" +
+                            "<div class='select2-result-repository__avatar' style='display:flex;align-items:center;'><img class='img-circle img-bordered-s' src='" + data.avatarUrl + "?s=40' /></div>" +
+                            "<div class='select2-result-repository__meta' style='margin-left:10px'>" +
+                                "<div class='select2-result-repository__username' style='font-size:16px;'></div>" +
+                                "<div class='select2-result-repository__email' style='font-size=13px;'></div>" +
+                            "</div>" +
+                        "</div>"
+                    );
+
+                    $container.find(".select2-result-repository__username").text(data.name);
+                    $container.find(".select2-result-repository__email").text(data.email);
+
+                    return $container;    
+                    {{-- return $('<div class="user-block"> \
+                        <img class="img-circle img-bordered-xs" src="' + escapeHtml(data.avatarUrl) + '?s=120" alt="User Image"> \
+                        <span class="username"> \
+                            <a href="#">' + escapeHtml(data.name) +'</a> \
+                        </span> \
+                        <span class="description"><strong>' + escapeHtml(data.email) + '</strong></span> \
+                    </div>'); --}}
+                },
+                templateSelection: function (data) {
+                        return $('<div> \
+                                    <span> \
+                                        <img class="img-rounded img-bordered-xs" src="' + escapeHtml(data.avatarUrl) + '?s=120" style="height:24px;margin-top:-4px;" alt="User Image"> \
+                                    </span> \
+                                    <span style="padding-left:10px;padding-right:10px;"> \
+                                        ' + escapeHtml(data.name) +' \
+                                    </span> \
+                                </div>');
+                    }
+                })
+            })
+
+        function toggleClass(id, className) {
+            document.getElementById(id).classList.toggle(className)
+        }
+        function escapeHtml(str) {
+            var div = document.createElement('div');
+            div.appendChild(document.createTextNode(str));
+            return div.innerHTML;
+        }
     </script>
 
 

+ 6 - 0
resources/views/layouts/main.blade.php

@@ -19,6 +19,9 @@
     {{--  datetimepicker --}}
     <link rel="stylesheet" href="{{asset('plugins/tempusdominus-bootstrap-4/css/tempusdominus-bootstrap-4.min.css')}}">
 
+    {{-- select2 --}}
+    <link rel="stylesheet" href="{{asset('plugins/select2/css/select2.min.css')}}">
+    
     <link rel="stylesheet" href="{{asset('css/app.css')}}">
     <link rel="preload" href="{{asset('plugins/fontawesome-free/css/all.min.css')}}" as="style"
           onload="this.onload=null;this.rel='stylesheet'">
@@ -344,6 +347,9 @@
 <!-- Datetimepicker -->
 <script src="{{asset('plugins/tempusdominus-bootstrap-4/js/tempusdominus-bootstrap-4.min.js')}}"></script>
 
+<!-- Select2 -->
+<script src={{asset('plugins/select2/js/select2.min.js')}}>
+
 <script>
     $(document).ready(function () {
         $('[data-toggle="popover"]').popover();

+ 1 - 1
routes/api.php

@@ -29,7 +29,7 @@ Route::middleware('api.token')->group(function () {
 
     Route::get('/notifications/{user}', [NotificationController::class, 'index']);
     Route::get('/notifications/{user}/{notification}', [NotificationController::class, 'view']);
-    Route::post('/notifications/{user}', [NotificationController::class, 'send']);
+    Route::post('/notifications', [NotificationController::class, 'send']);
     Route::delete('/notifications/{user}', [NotificationController::class, 'delete']);
     Route::delete('/notifications/{user}/{notification}', [NotificationController::class, 'deleteOne']);
 });

+ 3 - 2
routes/web.php

@@ -74,10 +74,11 @@ Route::middleware('auth')->group(function () {
 
         Route::resource('activitylogs', ActivityLogController::class);
 
+        Route::get("users.json", [UserController::class, "json"])->name('users.json');
         Route::get('users/loginas/{user}', [UserController::class, 'loginAs'])->name('users.loginas');
         Route::get('users/datatable', [UserController::class, 'datatable'])->name('users.datatable');
-        Route::get('users/{user}/notifications', [UserController::class, 'notifications'])->name('users.notifications');
-        Route::post('users/{user}/notifications', [UserController::class, 'notify'])->name('users.notifications');
+        Route::get('users/notifications', [UserController::class, 'notifications'])->name('users.notifications');
+        Route::post('users/notifications', [UserController::class, 'notify'])->name('users.notifications');
         Route::resource('users', UserController::class);
 
         Route::get('servers/datatable', [AdminServerController::class, 'datatable'])->name('servers.datatable');