feature(send push notification): send push notification to broker's followers
This commit is contained in:
parent
82715973dc
commit
0d0818baf3
@ -73,3 +73,6 @@ OTP_LIFESPAN=10
|
||||
|
||||
VAPID_PUBLIC_KEY=
|
||||
VAPID_PRIVATE_KEY=
|
||||
|
||||
# Same as the VAPID_PUBLIC_KEY
|
||||
VITE_VAPID_PUBLIC_KEY=BOBjjU2E-h8pDCV13yPwvMDR_WZwEhFmQY90gr16oJ5L1mpJ5qc7-0WzXcD1Z9D0Ozz0cLZxTe0_7nnDK3VFMP4
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
use App\Models\Broker;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Deal;
|
||||
use App\Models\Follow;
|
||||
use App\Notifications\NewDealNotification;
|
||||
|
||||
final readonly class SendDealCreatedNotificationCustomerAction
|
||||
@ -17,10 +16,12 @@ public function execute(Deal $deal): void
|
||||
*/
|
||||
$broker = $deal->broker->type;
|
||||
$followers = $broker->followers()->with('user')->get();
|
||||
$followers->map(function (Customer $customer) use ($deal) {
|
||||
\Log::info('dump', [$customer]);
|
||||
\Log::info("Sending notification to {$customer->user->name}");
|
||||
$customer->user->notify(new NewDealNotification($deal));
|
||||
$followers->map(function (Customer $follower) use ($deal) {
|
||||
$user = $follower->user;
|
||||
|
||||
\Log::info("Sending notification to {$follower->user->name}", [$deal, $follower->user]);
|
||||
|
||||
$user->notifyNow(new NewDealNotification($deal));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,11 +19,12 @@ public function index()
|
||||
public function approve(Deal $deal, SendDealCreatedNotificationCustomerAction $notificationCustomerAction)
|
||||
{
|
||||
try {
|
||||
\DB::transaction(function () use ($notificationCustomerAction, $deal) {
|
||||
\DB::transaction(function () use ($deal) {
|
||||
$deal->active = true;
|
||||
$deal->save();
|
||||
$notificationCustomerAction->execute($deal);
|
||||
});
|
||||
$notificationCustomerAction->execute($deal);
|
||||
|
||||
return back()->with('success', 'Deal activated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Deal activation Failed: ', [$e->getMessage(), $e->getTrace()]);
|
||||
|
||||
@ -3,14 +3,15 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\PushSubscriptionRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Container\Attributes\CurrentUser;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PushSubscriptionController extends Controller
|
||||
{
|
||||
public function __invoke(PushSubscriptionRequest $request): JsonResponse
|
||||
public function __invoke(#[CurrentUser] User $user, PushSubscriptionRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
$user = $request->user();
|
||||
$user->updatePushSubscription($data['endpoint'], $data['keys']['p256dh'], $data['keys']['auth']);
|
||||
|
||||
return response()->json(['message' => 'Push subscription updated successfully.']);
|
||||
|
||||
@ -22,7 +22,7 @@ public function authorize(): bool
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'endpoint' => 'required|string|max:255',
|
||||
'endpoint' => 'required|string|max:500',
|
||||
'keys.auth' => 'required|string|max:255',
|
||||
'keys.p256dh' => 'required|string|max:255',
|
||||
];
|
||||
|
||||
@ -27,7 +27,7 @@ public function toArray(Request $request): array
|
||||
'totalRedirection' => $this->total_redirection,
|
||||
'isLiked' => $this->is_liked,
|
||||
'isFavorite' => $this->is_favorite,
|
||||
'isFollowed' => $this->is_followed
|
||||
'isFollowed' => $this->is_followed,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use NotificationChannels\WebPush\HasPushSubscriptions;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
@ -62,7 +63,7 @@
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, HasPushSubscriptions;
|
||||
use HasFactory, HasPushSubscriptions, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
@ -3,15 +3,13 @@
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Deal;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Str;
|
||||
use NotificationChannels\WebPush\WebPushChannel;
|
||||
use NotificationChannels\WebPush\WebPushMessage;
|
||||
|
||||
class NewDealNotification extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
@ -32,9 +30,11 @@ public function via(object $notifiable): array
|
||||
|
||||
public function toWebPush($notifiable, $notification): WebPushMessage
|
||||
{
|
||||
\Log::info('Building WebPush for user: '.$notifiable->id);
|
||||
|
||||
return (new WebPushMessage)
|
||||
->title("New deal from {$this->deal->broker->name}")
|
||||
->body("Check out this deal: {$this->deal->title}")
|
||||
->body('Check out this deal:'.Str::limit($this->deal->title, 30, '...'))
|
||||
->action('View deal', route('explore', ['show' => $this->deal->id]));
|
||||
}
|
||||
}
|
||||
|
||||
44
config/webpush.php
Normal file
44
config/webpush.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* These are the keys for authentication (VAPID).
|
||||
* These keys must be safely stored and should not change.
|
||||
*/
|
||||
'vapid' => [
|
||||
'subject' => env('VAPID_SUBJECT'),
|
||||
'public_key' => env('VAPID_PUBLIC_KEY'),
|
||||
'private_key' => env('VAPID_PRIVATE_KEY'),
|
||||
'pem_file' => env('VAPID_PEM_FILE'),
|
||||
],
|
||||
|
||||
/**
|
||||
* This is model that will be used to for push subscriptions.
|
||||
*/
|
||||
'model' => \NotificationChannels\WebPush\PushSubscription::class,
|
||||
|
||||
/**
|
||||
* This is the name of the table that will be created by the migration and
|
||||
* used by the PushSubscription model shipped with this package.
|
||||
*/
|
||||
'table_name' => env('WEBPUSH_DB_TABLE', 'push_subscriptions'),
|
||||
|
||||
/**
|
||||
* This is the database connection that will be used by the migration and
|
||||
* the PushSubscription model shipped with this package.
|
||||
*/
|
||||
'database_connection' => env('WEBPUSH_DB_CONNECTION', env('DB_CONNECTION', 'mysql')),
|
||||
|
||||
/**
|
||||
* The Guzzle client options used by Minishlink\WebPush.
|
||||
*/
|
||||
'client_options' => [],
|
||||
|
||||
/**
|
||||
* The automatic padding in bytes used by Minishlink\WebPush.
|
||||
* Set to false to support Firefox Android with v1 endpoint.
|
||||
*/
|
||||
'automatic_padding' => env('WEBPUSH_AUTOMATIC_PADDING', true),
|
||||
|
||||
];
|
||||
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
|
||||
@ -71,6 +71,9 @@ export async function favorite(button) {
|
||||
*/
|
||||
export async function follow(button, brokerId) {
|
||||
let isFollowed = button.dataset.followed === 'true';
|
||||
if (!isFollowed) {
|
||||
await initSw();
|
||||
}
|
||||
try {
|
||||
button.dataset.followed = isFollowed ? 'false' : 'true';
|
||||
// Update other buttons
|
||||
@ -83,9 +86,7 @@ export async function follow(button, brokerId) {
|
||||
let response = await axios.post(`/api/follow/${brokerId}`);
|
||||
showToast(response.data.message);
|
||||
|
||||
if (!isFollowed) {
|
||||
initSw();
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
button.dataset.followed = isFollowed ? 'true' : 'false';
|
||||
showToast('Something went wrong!')
|
||||
|
||||
@ -20,11 +20,16 @@ export const initSw = async () => {
|
||||
* Request permission and trigger subscription
|
||||
*/
|
||||
const initPush = async () => {
|
||||
// Request permission
|
||||
if (Notification.permission === 'denied') {
|
||||
alert('You have blocked notifications. Please click the Lock icon in your address bar to enable them.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Request permission (this will only pop up if status is 'default')
|
||||
const permission = await Notification.requestPermission();
|
||||
|
||||
if (permission !== 'granted') {
|
||||
console.warn('Notification permission denied.');
|
||||
console.warn('Notification permission denied or dismissed.');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -39,27 +44,28 @@ const subscribeUser = async () => {
|
||||
|
||||
// Check if a subscription already exists
|
||||
let pushSubscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (!pushSubscription) {
|
||||
const subscribeOptions = {
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(
|
||||
import.meta.env.VITE_VAPID_PUBLIC_KEY
|
||||
)
|
||||
};
|
||||
|
||||
try {
|
||||
pushSubscription = await registration.pushManager.subscribe(subscribeOptions);
|
||||
console.log('New Subscription created.');
|
||||
} catch (err) {
|
||||
console.error('Failed to subscribe user:', err);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log('User is already subscribed.');
|
||||
if (pushSubscription) {
|
||||
await pushSubscription.unsubscribe();
|
||||
console.log('Unsubscribed old record...');
|
||||
}
|
||||
|
||||
// Send subscription to Laravel backend
|
||||
const subscribeOptions = {
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(
|
||||
import.meta.env.VITE_VAPID_PUBLIC_KEY
|
||||
)
|
||||
};
|
||||
|
||||
try {
|
||||
pushSubscription = await registration.pushManager.subscribe(subscribeOptions);
|
||||
console.log('New Subscription created.');
|
||||
} catch (err) {
|
||||
console.error('Failed to subscribe user:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Send a subscription to Laravel backend
|
||||
if (pushSubscription) {
|
||||
console.log('Received PushSubscription:', JSON.stringify(pushSubscription));
|
||||
await storePushSubscription(pushSubscription);
|
||||
|
||||
@ -14,6 +14,20 @@
|
||||
Route::get('/', HomeController::class)->name('home');
|
||||
Route::get('/explore', ExplorePageController::class)->name('explore');
|
||||
Route::post('/contact', ContactController::class)->name('contact');
|
||||
|
||||
Route::get('/test-openssl', function () {
|
||||
$res = openssl_pkey_new([
|
||||
'curve_name' => 'prime256v1',
|
||||
'private_key_type' => OPENSSL_KEYTYPE_EC,
|
||||
]);
|
||||
|
||||
if ($res === false) {
|
||||
return 'ERROR: '.openssl_error_string(); // likely "error:02001003:system library:fopen:No such process"
|
||||
}
|
||||
|
||||
return 'SUCCESS: OpenSSL is configured correctly.';
|
||||
});
|
||||
|
||||
/**
|
||||
* This routes are accessed by JS XHR requests, and is loaded here cause
|
||||
* we do not want to use sanctum for web requests
|
||||
|
||||
0
storage/logs/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
Loading…
x
Reference in New Issue
Block a user