How to send notification to specific users using Laravel Scheduler? - laravel

I ask this question while I have done researches to find clear solution for this problem but many of available answers are just how to use Laravel Scheduler itself!
So I am creating a simple website with Laravel to let users create reminders for themselves.Users can receive reminder either on their Emails or if they are logged in, they receive a notification alert something like Facebook Notifications.
I am not sure if I am following the right path or not but I'm using Laravel Scheduler to send reminders to each specific user.
I also use Events in the Laravel to push recent changes every 1 minute to users but my problem is, all users receive same notification. For example if I want to remind an appointment to Mr X, Ms Y also receives same exact notification!!!
At this point, I have two questions:
1 - Is using Laravel Scheduler a good idea for this project or not? If not then what technology or method is recommended?
2 - If I have to use Laravel Scheduler for this project, so how can I send notification to related user? (I cannot use User ID as Laravel Scheduler is running by server itself and not users!)
I also attached my codes to show you what I have up to this point and I know the method I used in Laravel Channels Broadcast is somehow wrong but I just tried it!If anyone knows the answer please help me. Thank you
Laravel Scheduler - Command - Handle Function
public function handle()
{
$reminders = Note::whereTime('task_date', '<=', Carbon::now()->addMinutes(30))
->WhereTime('task_date', '>', Carbon::now()->subMinutes(30))->get();
foreach ($reminders as $reminder) {
if (!$reminder->notified) {
Note::find($reminder->id)
->update(['notified' => 1]);
event(new RemindUsers($reminder));
dd($reminder->title);
}
}
}
Laravel Event
public $reminder;
public function __construct($reminder)
{
$this->reminder = $reminder;
}
public function broadcastOn()
{
return new PrivateChannel('remind.'.$this->reminder->user_id);
}
public function broadcastWith () {
return [
'reminders' => $this->reminder
];
}
Laravel Channels
Broadcast::channel('remind.{id}', function ($user, $id) {
$notes = Note::where('user_id', $id)->get();
foreach ($notes as $note) {
return $user->id === $note->user_id;
}
});
Vue JS Code - Echo Method
data () {
return {
user: 1
}
},
CatchReminders () {
Echo.private(`remind.${this.user}`)
.listen('RemindUsers', (response) => {
this.$toast.info(`${response.reminders.title}`)
})
}

Related

Laravel Livewire Echo configuration

I've setup a notification system with Pusher and Echo on my Laravel 8 app. It works fine, I'm able to retrieve the notification event in VanillaJS with
window.Echo.private('App.Models.User.' + User.id)
.notification((notification) => {
if (notification.type === 'App\\Notifications\\JobLiked') {
let count = document.getElementById('count');
let number = count.innerHTML;
number++;
count.innerHTML = number;
}
});
But now I want to use Livewire listeners to trigger my function, then I setup :
public function getListeners()
{
return [
"echo-private:App.Models.User.{$this->authId},NotificationSent" => 'notifyNewJobLiked',
];
}
But nothing seems to work and I have no error message.. do you have any clue what could possibly going on ?
Thank you very much ! :)
Try to configure your listener with the following specific event name:
public function getListeners()
{
return [
"echo-private:App.Models.User.{$this->authId},.Illuminate\\Notifications\\Events\\BroadcastNotificationCreated" => 'notifyNewJobLiked',
];
}
Since you are using Laravel Notifications to trigger the broadcast instead of a broadcastable event, the event name when fired defaults to Illuminate\\Notifications\\Events\\BroadcastNotificationCreated.
In Echo there are two methods to listen for incoming messages: notification and listen. The reason why it works with your vanilla js is that you are using the notification method, whereas the livewire event listener only works with Echo's listen, which expects the name of the calling event.
If you are using pusher, you can see the name of the calling event in the pusher debug console.
Also take care and add a dot in front of the namespaced event as described in the documentation.
Right way to get Echo notification in livewire:
public function getListeners()
{
$user_id = auth()->user()->id;
return [
"echo-notification:App.Models.User.{$user_id}, notification" => 'gotNotification'
];
}
As you can see the channel type is notification, not private:
echo-notification
Also don't forget to have a record authenticating the users in your channels route, same as you do for a private channel:
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
Another thing I learned is that you can get the notification channel data from the returned field.
So you can do:
public function gotNotification($notification)
{
}

Laravel: Check if new items added to db table and schedule a job to email user

I want to trigger an email when new rows are added to a table in my Laravel application. However I want to add a buffer of sorts, so if 5 rows are added in quick succession then only 1 email is sent.
The method I've chosen is to schedule a check every 15 minutes and see if there are new rows added. If there are then I will queue an email.
Currently I'm getting an error on the schedule. I'll run through my code below:
In Kernel.php where we setup schedules I have:
$schedule->job(new ProcessActivity)
->everyFifteenMinutes()
->when(function () {
return \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->exists();
})
->onSuccess(function () {
Log::debug(
'Success'
);
})
->onFailure(function () {
Log::debug(
'Fail'
);
});
Which I use to trigger the Job found in: App\Jobs\ProcessActivity.php :
public function __construct()
{
$this->jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(15), Carbon::now()))->get();
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
Log::debug('Activity Job Run', ['jobs' => $this->jobs]);
$this->jobs->each(function ($item, $key) {
Log::debug('loop');
// get project
$project = $item->project;
// get project email
$user_id = $project->user_id;
$email = \App\User::find($user_id)->email;
// get project UUID
$projectUuid = $project->public_id;
// emails
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// create activity email
Notification::route('mail', $subscriberEmails)->notify(new Activity($project, $projectUuid));
});
return true;
}
I've posted my full code above which also shows a relationship between my JobItems and Project models. I won't elaborate on that as I've commented in the code.
The problem
When I add a new row to my JobItem table I can see the job is scheduled and processed (using Laravel Telescope to inspect this).
However, I can also see in my log that for each job I get two log messages:
First: 'Fail' and then 'Activity Job Run'
My email is not sent and I'm uncertain how to determine why this is failing.
So it seems that onFailure is being triggered and there is a problem with my ProcessActivity.
Any clues on where I am going wrong and how to determine the error would be much appreciated.
I have a fix, but first, here are some things I learnt that hampered my progress:
I was using this artisan command to process my scheduled jobs:
php artisan queue:work
The problem with developing while using that command is that if there are code changes then those changes are not recognised.
So you can either Command+C to return to the console and use this every time there is a code change:
php artisan queue:restart
php artisan queue:work
Or you can just use this and it will allow code changes:
php artisan queue:listen
As you can imagine without knowing this you will have a slow debugging process!
As a result of this and adding an exception to my Job I made some progress. I'll paste in the code below to compare against the original code:
public function __construct()
{
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
try {
$jobs = \App\JobItem::whereBetween('created_at', array(Carbon::now()->subMinutes(20), Carbon::now()))->get();
Log::debug('Activity Job', ['jobs' => $jobs]);
// collection start
$collection = collect();
// loop jobs to get emails
foreach ($jobs as $key => $value) {
// get project UUID
$project = $value->project;
$projectUuid = $project->public_id;
// get emails subscribed to projects
$subscriberEmails = \App\ProjectSubscription::where('project_id', $project->id)->get();
// merge into a single collection via the loop
if ($key != 0) {
$merge = $collection->merge($subscriberEmails);
$collection = collect($merge);
} else {
$collection = $subscriberEmails;
}
// Log::debug('emails_project in loop', ['emails' => $subscriberEmails]);
};
// clean object with uniques only
$subscriberEmailsCleaned = $collection->unique();
// debug
Log::debug('Project Emails to Notify', ['emails' => $subscriberEmailsCleaned]);
// create activity email
Notification::route('mail', $subscriberEmailsCleaned)->notify(new Activity($project, $projectUuid));
} catch (\Exception $e) {
\Log::info($e->getMessage());
}
}
First thing to note, is that as __construct() is run initially and is serialised. Then the handle method is called when the job is processed. So I had to move my eloquent query into the handle method.
I also used a foreach rather than .each to loop through and create a new collection of emails. Perhaps there is a more elegant way, but I needed to create a collection of emails and this way allowed me to move the variables in the loop outside to be used in the method.
You can see me merge these at the bottom of the loop.
I have also added a few Log:: items which is useful for debugging.
Not fixed 100%
With this code I can now auto schedule an email every x minutes when new items are added. However, I am still getting the log Fail from the onFailure()from my Kernal.php file:
->onFailure(function () {
Log::debug(
'Fail'
);
I am still confused as to what that indicates and how I can determine more information about how this has failed and what that means. However, it does work so I will cautiously move forward (with one eye open on the comments, in case someone has an idea that can help!)

Laravel : Broadcast event with PresenceChannel

I'm trying to broadcast an event in a case where two user share a conversation (like a chat) where only the two of the can access and get notified when a new message comes.
I think, after reading the documentation that the presence channel is the best option (private is for one people and the server and channel is for something plublic without checking ?)
So in my routes/channels.php I have something like this:
Broadcast::channel('conversation.{conversation}', function ($user, Conversation $conversation) {
if ($user->id === $conversation->user1_id || $user->id === $conversation->user2_id) {
return $user;
}
return null;
});
In the client side a Component I have:
Echo
.join('conversation.${conversation.id}')
.here((users) => {
this.usersInRoom = users;
})
.joining((user) => {
this.usersInRoom.push(user);
})
.leaving((user) => {
this.usersInRoom = this.usersInRoom.filter(u => u != user);
})
.listen('MessagePosted', (e) => {
this.messages.push({
id :e.message.id,
user_id :e.message.user_id,
conversation_id :e.message.conversation_id,
user :e.user,
text :e.message.text,
created_at :e.message.created_at,
updated_at :e.message.updated_at,
});
});
And the class that emit the even MessagePosted :
public function broadcastOn()
{
return new PresenceChannel('conversation.'.$this->message->conversation_id);
}
So I now that previously I used a PresenceChannel without any checking, so if two people where in there conversation they would get notification from everyone. not the right thing.
In the server side, I have the 'conversation.{conversation}' that was mentioned in the documentation to make a separated channel. But I also saw something like 'conversation.*'.
And on the client side I have join('conversation.${conversation.id}') but here I am not sure at all I just know that I have in the props (props : ['user', 'friend', 'conversation']) a conversation which is an object with the id of the conversation.
So When, when everyone was on the same channel with no restriction everything was working perfectly and now, I think I have an error which make the whole thing not work.
I have two 500 server side error when I load a client conversation :
ReflectionException in Broadcaster.php line 170:
Class Conversation does not exist
(And in the route/channels.php I import the Conversation class use App\Conversation;)
and
HttpException in Broadcaster.php line 154:
I found the following solution to my problem. I think I saw something in the documentation saying that you might need to use the Path of the class you want to broadcast. So I tried something like this :
In the routes/channel.php
Broadcast::channel('App.Conversation.{id}', function ($user, $id) {
$conversation = Conversation::findOrFail($id);
if ($user->id === $conversation->user1_id || $user->id === $conversation->user2_id) {
return $user;
}
return null;
});
In the client side in the vue component :
Echo
.join('App.Conversation.'+this.conversation.id)
...
In the class that emit the event MessagePosted :
public function broadcastOn()
{
return new PresenceChannel('App.Conversation.'.$this->message->conversation_id);
}

How do I send mails to a list of emails with queue jobs using SMTP

I've an application that sends a mail to list of emails after submit the form. For this I'm using queue jobs to send mail to that list in the background. I'm using SMTP for this with mailtrap. I'm new to this queue jobs functionality.
this is my controller code to dispatch queue:
public function sendMail(Request $request)
{
$lists = List::where('list_id',$request->list_id)->pluck('email')->toArray();
$jobs = (new SendEmailToList($lists));
$this->dispatch($jobs);
return 'success';
}
And this is my job functionality in queue :
public function handle()
{
$lists = $this->lists;
Mail::send('email.test', array('email' => 'Sample'), function ($message) use ($lists) {
$message->to($lists);
});
}
I've a program file in the supervisor of my Linux system to queue:listen.
I've done all things but still it's not sending mail to all the list of emails. I've referred many of documentations but still same issue, all the documents given that to implement a QueueManager.
You should probably call Mail::queue or Mail::later instead of Mail::send.
Please refer to Mail documentation in case you've been missing something else.

Getting Queued Jobs response Laravel 5.2

currently I have the following set up, a route that is calling a function in my controller that is in turn queuing a job.
//My Route
Route::get('/testJob', 'Controller#testJob');
//My Controller
public function testJob()
{
$job = (new testJob())->delay(5);
$this->dispatch($job);
}
//My job
public function handle()
{
require 'testAPICall.php';
// echo $response;
return $response;
}
//testAPICall.php
$response = 'this is the response';
//Queue After
Queue::after(function (JobProcessed $event) {
echo var_dump($event->data);
});
What I would like to be able to do, is access the response returned by the job in Queue::after, or alternatively, pass a callback into the queue to be execute after the job, again with access to the response from the job.
Is this something that is possible with Laravel Queues, and if so how would I go about this?
Cheers, Jack.
Queue::after() is a global callback, that will run after each job. So this might not what you want.
In your case, I would depend on Events/Listeners to be triggered after finishing the job.
public function handle(Mailer $mailer)
{
//Your code
event(new JobDone($data));
}
Please let me know if you need more details for implementation.
I have done something like yours that log a message "queue.txt" in laravel 5 "app" folder
This code I've got from a youtube video and not my code , but I have tested it successfully
First thing you have to code in "Routes.php" as below
Route::get('/',function()
{
//$queue = Queue::push('LogMessage',array('message'=>'Time: '.time()));
$queue = Queue::later(20,'LogMessage',array('message'=>'Time: '.time()));
return $queue;
});
class LogMessage{
public function fire($job,$data){
File::append(app_path().'/queue.txt',$data['message'].PHP_EOL);
$job->delete();
}
}
Then you can run your project folder using "php -S localhost:8888 -t public"
at the same time you must open a another terminal window in windows or linux environment and pointed to same folder and issue the command "php artisan queue:listen"
I think this will be helpful for you!

Resources