Send pusher notification to only 1 user in Laravel - laravel

So I'm using Laravel Event with Pusher, so far if I use public channel without any extra conditions I can get my data as notification in my blade.
The problem starts when I try to send notification to 1 user only and not everyone, I've tried PrivateChannel but I'm getting:
[Vue warn]: Error in created hook: "ReferenceError: bidId is not defined"
and
ReferenceError: bidId is not defined
Logic
Send notification to the project owner only (where his/her ID is saved in projects table)
Vue Component
<template>
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link icon-menu dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
<span class="sr-only">Notifications</span>
<i class="far fa-bell"></i>
<span class="notification-dot"></span>
</a>
<div class="dropdown-menu notifications dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-header"><strong>You have {{bids.length}} new notifications</strong></a>
<a class="dropdown-item" v-for="bid in bids">
<div class="media">
<div class="media-left">
<i class="fa fa-fw fa-flag-checkered text-muted"></i>
</div>
<div class="media-body">
<p class="text">{{bid.message}}</p>
<span class="timestamp">{{bid.created_at}}</span>
</div>
</div>
</a>
<a class="dropdown-item more">See all notifications</a>
</div>
</li>
</template>
<script>
export default {
data() {
return{
bids:[],
}
},
created(){
this.fetchBids();
},
methods:{
fetchBids(){
// Echo.channel('bidplaced') //tested with public channel
Echo.private(`bidplaced.${bidId}`)
.listen('BidPlaced', (e) => {
this.bids.push(e.bid);
});
},
},
}
</script>
Blade
<notifications></notifications>
Controller
$bid->save();
event(new BidPlaced($bid)); //firing the event
// this is 1 way to get the user i need to receive notification
$user = $bid->project->user;
Event
class BidPlaced implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $bid;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Bid $bid)
{
$this->bid = $bid;
}
public function broadcastOn()
{
// return ['bidplaced']; //this is working as public channel
return new PrivateChannel('bidplaced.'.$this->bid->project->user_id);
}
Question
How can I get my project owner as receiver of those notifications?

Possibly a little late but hopefully this helps someone!
Controller
broadcast(new BidPlaced($user))->toOthers();
Event
use App\User
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function broadcastOn()
{
return new PrivateChannel('notification.'.$this->user->id);
}
Vue component
created() {
Echo.private('notification.'+auth.user.id)
.listen('BidPlaced', (e) => {
console.log(e);
});
},

Related

Laravel Livewire Message only to a specific user

I took over a working chat application. to send a message, the original code uses a user list, which then checks whether a message exists.
In this case, it will redirect to the chat blade. If not, a new message will be created and then redirected to Chat blade.
Github to the app
https://github.com/thee-king-yodah/laravel-livewire-chat-message-application
I would like to change this and use a button on a profile page, but I'm currently working on the implementation and get stuck.
If I add the livewire into my blade it list a button for all users.
Controller: CreateChat
namespace App\Http\Livewire\Chat;
use Livewire\Component;
use App\Models\User;
use App\Models\Conversation;
use App\Models\Message;
use GuzzleHttp\Promise\Create;
use Illuminate\Support\Carbon;
use App\Http\Livewire\Chat\ChatList;
class CreateChat extends Component
{
public $users;
public $message= 'Start';
public function checkconversation($receiverId)
{
//dd($this->spieler);
$checkedConversation = Conversation::where('receiver_id', auth()->user()->id)
->where('sender_id', $receiverId)
->orWhere('receiver_id', $receiverId)
->where('sender_id', auth()->user()->id)->get();
if (count($checkedConversation) == 0) {
//dd('no conversation');
$createdConversation= Conversation::create(['receiver_id'=>$receiverId,'sender_id'=>auth()->user()->id,'last_time_message'=>Carbon::now()]);
$createdMessage= Message::create(['conversation_id'=>$createdConversation->id,'sender_id'=>auth()->user()->id,'receiver_id'=>$receiverId,'body'=>$this->message]);
$createdConversation->last_time_message= $createdMessage->created_at;
$createdConversation->save();
return redirect()->to('/chat');
//dd($createdMessage);
//dd('saved');
} else if (count($checkedConversation) >= 1) {
return redirect()->to('/chat');
//dd('conversation exists');
}
# code...
}
public function render()
{
$this->users = User::where('id','!=',auth()->user()->id)->get();
return view('livewire.chat.create-chat');
}
Create-chat.blade
<div>
<div class="flex justify-center">
<div class="bg-white rounded-lg border border-gray-200 w-96 text-gray-900">
<ul role="list" class="">
#foreach($users as $user)
<div class="flex justify-center mb-2 mt-4">
<button type="button" class="inline-block align-middle text-center select-none border font-normal whitespace-no-wrap rounded py-1 px-3 leading-normal no-underline text-blue-600 border-blue-600 hover:bg-blue-600 hover:text-white bg-white hover:bg-blue-600 ms-1"
wire:click='checkconversation({{ $user->id }})'>Nachricht</button>
</div>
#endforeach
</ul>
</div>
</div>
</div>
Controller Profile
amespace App\Http\Livewire;
use Livewire\Component;
use App\Models\User;
use Illuminate\Support\Carbon;
class Playerprofil extends Component
{
public $player;
public function render()
{
return view('livewire.playerprofile', [
'player' => $this->player,
]);
}
public function mount($id)
{
$this->player= User::find($id);
}
Profil.blade
<div>
#livewire('chat.create-chat' )
</div>
I would be very grateful if someone could help m out.
It's solved.
Controller: CreateChat
added mount
public function mount()
{
$this->users = User::find($this->user_id);
}
removed foreach in CreateChat.blade
<div>
#livewire('chat.create-chat', ['user_id' => $player->id ])
</div>

Why do I get "Failed to load resource" when I mount the component in Vue.js?

I have two errors in my Vue components due to when I mount the component. The console log report shows me these 404 errors
1)[Error] Failed to load resource: the server responded with a status of 404 (Not Found) localhot8000/movies/getComments
2)[Error] Failed to load resource: the server responded with a status of 404 (Not Found) localhot8000/movies/comment/store
It cannot find my function, but I've look my code and I don't know how to fix this error.
Follow the code of my route on web.php:
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::resource('movies', App\Http\Controllers\MovieController::class);
Route::post('/comment/store','CommentController#store');
Route::get('/getComments/{movie}','CommentController#getComments');
Follow my code of my show.blade.php
#extends('layouts.app')
#section('content')
<div class="container my-2">
<div class="card mb-3">
<img src="{{ asset('images/movies/' . $movie->image) }}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">{{$movie->title}}</h5>
<p class="card-text"> {{$movie->description}}</p>
<p class="card-text"><small class="text-muted">{{$movie->actor}}</small></p>
<p class="card-text"><small class="text-muted">{{$movie->gender}}</small></p>
<p class="card-text"><small class="text-muted"> {{$movie->duration}}</small></p>
</div>
</div>
</div>
<div class="card-body">
<comment-component :userid="{{Auth::user()->id}}" :movieid="{{$movie->id}}"></comment-component>
<get-comments-component :userid="{{Auth::user()->id}}" :movieid="{{$movie->id}}"></get-comments-component>
</div>
</div>
<span></span>
<button type="button" class="btn btn-warning">Back to Movies</button>
</tr>
</div>
</thead>
#endsection
Follow my code of my CommentComponent.vue
<template>
<div class="card text-center">
<div class="card-header">
Comments
</div>
<div class="card-body">
<h5 class="card-title">Nome user</h5>
<textarea name="comment" :v-model="formData.comment" class="form-control mb-1" rows="2" placeholder="Write a comment here..." ></textarea>
<button class="btn btn-success float-right" #click="commentStore">Add Comment</button>
</div>
<div class="card-footer text-muted">
2 days ago
</div>
</div>
</template>
<script>
export default {
props:['userid','movieid'],
data(){
return{
formData:{
comment:'',
user_id:this.userid,
movie_id:this.movieid
}
}
},
methods:{
commentStore(){
axios.post('comment/store',this.formData).then((response)=>{
console.log(response.data)
this.formData.comment=''
}).catch((error)=>{
console.log(error)
});
}
}
}
</script>
Follow my code of my GetCommentsComponent.vue
<template>
<div class="card text-center">
<div class="card-header">
Comments
</div>
<div class="card-body">
<h5 class="card-title">Nome user</h5>
<p v-for="(comment,index) in comments" :key="index">
<span class="badge badge-pill badge-light">{{ comment.user.name }} Commented: </span> {{ comment.comment }}
</p>
</div>
<div class="card-footer text-muted">
2 days ago
</div>
</div>
</template>
<script>
export default {
props:['userid','movieid'],
data(){
return{
comments:{}
}
},
mounted(){
this.getComments()
this.interval=setInterval(function(){
this.getComments()
}.bind(this),500)
},
methods:{
getComments(){
axios.get('getComments/'+this.movieid).then((response)=>{
this.comments = response.data
}).catch((errors)=>{
console.log(errors)
});
}
}
}
</script>
Follow my cod of my App.js
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue').default;
/**
* The following block of code may be used to automatically register your
* Vue components. It will recursively scan this directory for the Vue
* components and automatically register them with their "basename".
*
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/
// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
Vue.component('comment-component', require('./components/CommentComponent.vue').default);
Vue.component('get-comments-component', require('./components/GetCommentsComponent.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#app',
});
Follow my code of my Commentcontroller
<?php
namespace App\Http\Controllers;
use App\Models\Movie;
use App\Models\Comment;
use Illuminate\Http\Request;
class CommentController extends Controller
{
public function store(Request $request)
{
//$request->all();
$comment = new Comment;
$comment->insert([
'movie_id' => $request->movie_id,
'user_id' => $request->user_id,
'comment' => $request->comment,
]);
return response()->json($comment);
}
public function getComments(Movie $movie)
{
return response()->json($movie->comments()->with('user')->latest()->get());
}
}
Code for my Eloquent models:
User
public function movies()
{
return $this->hasMany('App\Movie');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
Movie
public function user(){
return $this->belongsTo('App\User');
}
public function comments(){
return $this->hasMany('App\Comment');
}
}
Comment
public function user(){
return $this->belongsTo('App\User');
}
public function movie(){
return $this->belongsTo('App\Movie');
}
}
The issue isn't being caused by Vue failing to mount the components. The HTTP 404 errors are due to your axios() calls trying to load incorrect URLs. You're using relative URLs which will be affected by the current page URL.
If we look at your routes, we have these two entries:
Route::post('/comment/store','CommentController#store');
Route::get('/getComments/{movie}','CommentController#getComments');
Which create the following URLs:
localhot:8000/comment/store
localhot:8000/getComments/{movie}
You can use php artisan route:list to see a list of the URLs available in your Laravel app.
When we look at the two Vue components that reference these URLs, you'll see the following:
axios.get('getComments/'+this.movieid).then((response)=>{ ...
axios.post('comment/store',this.formData).then((response)=>{ ...
You're using a route::resource('movies') Resource Controller, so your show() method will have the URL of localhot:8000/movies/{id}.
This means you're Ajax calls are expecting URLs of:
localhot:8000/movies/comment/store
localhot:8000/movies/getComments/{movie}
which leads to the HTTP 404 errors.
The fix should be a quick one: your just need to update your Axios calls to ensure they're referring the correct URLs:
axios.get('/getComments/'+this.movieid).then((response)=>{ ...
^ added forward slash to get correct URL
axios.post('/comment/store',this.formData).then((response)=>{ ...
^ added

laravel event generate an error in real chat app

I am working on a demo for instant chat and I was able to display the number of logged in users and show their names in the "Online Users" list, but the problem is that I created a laravel event to show messages in real time, and here I get the following error message in my console: Error: Syntax error, unrecognized expression: #user=1 .
demo app details :
laravel : 5.8.*
php : ^7.1.3
redis & laravel echo & laravel echo serveur
view :
<div class="container">
<div class="row">
<div class="col-md-4">
<h2>Online Users</h2>
<hr>
<h5 id="no-online-users">No Online Users</h5>
<ul class="liste-group" id="online-users">
</ul>
</div>
</div>
<div class="row">
<div class="col-md-9 d-flex flex-column" style="height: 80vh">
<div class="h-100 bg-white mb-4 p-5" id="chat" style="overflow-y: scroll;">
#foreach($messages as $message)
#if(\Auth::user()->id == $message->user_id)
<div class="mt-4 w-50 text-white p-3 rounded float-right bg-primary">
#else
<div class="mt-4 w-50 text-black p-3 rounded float-left bg-warning">
#endif
<p>{{ $message->body }}</p>
</div>
<div class="clearfix"></div>
#endforeach
</div>
<form action="" class="d-flex">
<input type="text" id="chat-text" name="" data-url="{{ route('messages.store') }}" style="margin-right: 10px" class="col-md-9 d-flex flex-column">
<button class="btn btn-primary col-md-3">Send</button>
</form>
</div>
</div>
</div>
MessageController :
namespace App\Http\Controllers;
use App\Message;
use Illuminate\Http\Request;
class MessageController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
//index
public function index()
{
$messages = Message::all();
return view('messages.index',compact('messages'));
}
// store
public function store(Request $request)
{
//$message = auth()->user()->messages()->create($request->all());
//return $request->body;
$message = new Message();
$message->user_id = \Auth::user()->id;
$message->body = $request->body;
$message->save();
broadcast(new MessageDelivered($message))->toOthers();
}
}
the event MessageDelivered:
namespace App\Events;
use App\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class MessageDelivered implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('chat-group');
}
}
app.js
require('./bootstrap');
import Echo from "laravel-echo"
window.io = require('socket.io-client');
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
// online users :
let onlineUsersLength = 0;
window.Echo.join('online')
.here((users) => {
onlineUsersLength = users.length;
console.log(onlineUsersLength);
let userId = $('meta[name=user-id]').attr('content');
//console.log(userId);
users.forEach(function(user){
if (user.id == userId) { return; }
$('#no-online-users').css('display','none');
$('#online-users').append('<li id="user='+user.id+'" class="liste-group-item">'+user.name+'</li>');
})
//console.log(users);
})
.joining((user) => {
$('#no-online-users').css('display','none');
$('#online-users').append('<li id="user='+user.id+'" class="liste-group-item">'+user.name+'</li>');
})
.leaving((user) => {
$('#user='+user.id).css('display','none');
$('#no-online-users').css('display','yes');
});
// submit chat text :
$('#chat-text').keypress(function(e){
//console.log(e.which);
if(e.which == 13){
e.preventDefault();
let body = $(this).val();
let url = $(this).data('url');
let data = {
'_token': $('meta[name=csrf-token]').attr('content'),
body
}
//console.log(body);
$.ajax({
url: url,
method: 'post',
data: data,
});
}
});
window.Echo.channel('chat-group')
.listen('MessageDelivered', (e) => {
console.log('message');
});
problem :
in first user console (user id 1 in database)
in second user console (user id 2 in database)
When I refresh the page for a specific user, the error appears for the second user
I guess you have a typo here $('#user='+user.id).css('display','none')
^^^
and here $('#online-users').append('li id="user='+user.id+'" class="liste-group-item">'+user.name+'</li>'); ^^^
You may fix it
//...
users.forEach(function(user){
if (user.id == userId) { return; }
$('#no-online-users').css('display','none');
$('#online-users').append('<li id="user-'+user.id+'" class="liste-group-item">'+user.name+'</li>');
})
//...
.joining((user) => {
$('#no-online-users').css('display','none');
$('#online-users').append('<li id="user='+user.id+'" class="liste-group-item">'+user.name+'</li>');
})
.leaving((user) => {
$('#user-'+user.id).css('display','none');
$('#no-online-users').css('display','yes');
});
//...

How to send variable from blade to controller without changing the url

In blade I have a list of books. I want to choose a specific book to show its information. And to do so I want to send with href the id of the book to my controller passing through route.
For example i have
<div class="body text-center">
<h6><b>{{($book->getName())}}</b></h6>
</div>
In href I want to add $bookId = $book->id and the route name so I can call the route with the specific name which calls a method in a controller which can use the variable $bookId
Route::get('/infromation','Books\BookController#index')->name('info');
Here's two propositions:
The first one is to use spatie/laravel-sluggable to have the book name in the URL
The second one is to access the book without changing the URL with a POST request
Using spatie/laravel-sluggable
The slug will be generated automatically from name when the book is created.
your-migration.php
Schema::create('books', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('slug')->unique()->index();
$table->string('name');
// ...
$table->timestamps();
});
web.php
// Change the URIs as you want. `{book}` is mandatory to retrieve the book though.
Route::get('/books','Books\BookController#index')->name('book.index');
Route::get('/books/{book}','Books\BookController#show')->name('book.show');
Book.php
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Book extends Model
{
use HasSlug;
protected $guarded = [];
public function getSlugOptions()
{
// Adapt with what you want
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug')
->doNotGenerateSlugsOnUpdate();
}
public function getRouteKeyName()
{
return 'slug';
}
}
BookController.php
class BookController extends Controller
{
public function index()
{
return view('book.index');
}
public function show(Book $book)
{
// $book is retrieving using Model Binding: https://laravel.com/docs/5.8/routing#route-model-binding
return view('book.show', compact('book'));
}
}
index.blade.php
<div class="body text-center">
<a href="{{ route('book.show', $book) }}">
<h6><b>{{ $book->getName() }}</b></h6>
</a>
</div>
Using POST request (URI does not change) and without SLUG
I wouldn't recommend using this for the user experience.
The user cannot bookmark the book or share the link with someone else
When refreshing the page, it will prompt to the user if he want to re-submit the form request
web.php
Route::get('/books','Books\BookController#index')->name('book.index');
Route::post('/books','Books\BookController#show')->name('book.show');
BookController.php
class BookController extends Controller
{
public function index()
{
return view('book.index');
}
public function show()
{
$book = Book::findOrFail(request('book_id'));
return view('book.show', compact('book'));
}
}
index.blade.php
<div class="body text-center">
<form action="{{ route('book.show') }}" method="POST">
#csrf
<input type="hidden" value="{{ $book->id }}" name="book_id">
<h6>
<button type="submit">
<b>{{ $book->getName() }}</b>
</button>
</h6>
</form>
</div>
You can remove the default button style to make it looks like a link
https://stackoverflow.com/a/45890842/8068675
You can try like this
<form action="/BookName/information/<?php echo $book->id; ?>" method="post">
<div class="body text-center">
<input type="hidden" name="book_id" value="{{ $book->id }}">
<a href="/information/<?php echo $book->id; ?>">
<button type="submit" name="book_information" class="btn btn-primary">
<h6>
<b>{{($book->getName())}}</b>
</h6>
</button>
</div>
</form>
// make route like this
Route::post('/BookName/information/{id}','Books\BookController#index');
// Access the that id in controller
public function index(Request $request)
{
echo $request->book_id;
}

How to load model relationships inside an event Laravel 5.3

I have an event for when a new article is created. On another page I am listening for this event (VueJs and Laravel Echo), and then appending the newly created article to the articles list (actually unshifting), which then updates the view reactively. However, an article has an author which is related to the users table. I keep getting error and Vue keeps crashing because the article doesn't have an author attribute. Which is because the author is a relationship. I have tried putting $this->article->load('author') in the __construct method of the event itself, and I've tried using load('author') before sending the article to the event. Neither have worked at all. How can I maintain this relationship in the event so that it will be sent in the broadcast, in turn allowing Vue to access it as a property?
Template:
<template>
<div>
<div class="article-preview-container" v-for="article in articles">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><a :href="article.slug">{{ article.title}}</a></h2>
</div>
<div class="panel-body">
<p class="lead">
<span class="glyphicon glyphicon-time"></span> {{ article.created_at }} |
<span class="glyphicon glyphicon-user"></span> {{ article.author.full_name }}
</p>
<div class="article-preview">
<img :src="article.main_image" :alt="article.title">
<p>{{ article.preview }}</p>
</div>
</div>
</div>
</div>
<infinite-loading :on-infinite="onInfinite" ref="infiniteLoading" spinner="spiral">
<span slot="no-more">
There are no more articles to display :(
</span>
</infinite-loading>
</div>
</template>
<script>
import InfiniteLoading from 'vue-infinite-loading';
export default {
mounted() {
Echo.channel('articles').listen('ArticleCreated', (event) => {
console.log(event.article.author);
this.articles.unshift(event.article);
});
},
data() {
return {
articles: [],
skip: 0,
};
},
methods: {
onInfinite() {
axios.get('/articles/' + this.skip).then(function (response) {
if(response.data.length > 0) {
this.articles = this.articles.concat(response.data);
this.$refs.infiniteLoading.$emit('$InfiniteLoading:loaded');
if(response.data.length < 5) {
this.$refs.infiniteLoading.$emit('$InfiniteLoading:complete');
} else {
this.skip += 5;
}
} else {
this.$refs.infiniteLoading.$emit('$InfiniteLoading:complete');
}
}.bind(this));
},
},
components: {
InfiniteLoading,
},
};
</script>
Event:
<?php
namespace App\Events;
use App\Article;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ArticleCreated implements ShouldBroadcast {
use InteractsWithSockets, SerializesModels;
public $article;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Article $article) {
$this->article = $article;
//tried $this->article->load('author')
//tried $this->article = $article->with('author')
//tried loading author using just $this->article->author
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn() {
return new Channel('articles');
}
}

Resources