started working in notifications
This commit is contained in:
parent
fd2f08ca78
commit
1baabb7649
14
.env.example
14
.env.example
@ -36,7 +36,7 @@ SESSION_ENCRYPT=false
|
|||||||
SESSION_PATH=/
|
SESSION_PATH=/
|
||||||
SESSION_DOMAIN=null
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
BROADCAST_CONNECTION=log
|
BROADCAST_CONNECTION=reverb
|
||||||
FILESYSTEM_DISK=local
|
FILESYSTEM_DISK=local
|
||||||
QUEUE_CONNECTION=database
|
QUEUE_CONNECTION=database
|
||||||
|
|
||||||
@ -66,3 +66,15 @@ AWS_BUCKET=
|
|||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
REVERB_APP_ID=
|
||||||
|
REVERB_APP_KEY=
|
||||||
|
REVERB_APP_SECRET=
|
||||||
|
REVERB_HOST="localhost"
|
||||||
|
REVERB_PORT=8080
|
||||||
|
REVERB_SCHEME=http
|
||||||
|
|
||||||
|
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
||||||
|
VITE_REVERB_HOST="${REVERB_HOST}"
|
||||||
|
VITE_REVERB_PORT="${REVERB_PORT}"
|
||||||
|
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
|
||||||
|
10
TODO.md
10
TODO.md
@ -16,10 +16,10 @@
|
|||||||
- [x] Boosts
|
- [x] Boosts
|
||||||
- [x] Local Boost
|
- [x] Local Boost
|
||||||
- [x] Tags
|
- [x] Tags
|
||||||
- [ ] Mentions
|
- [x] Mentions
|
||||||
- [ ] Local mentions
|
- [x] Local mentions
|
||||||
- [ ] Private post
|
- [ ] Private post
|
||||||
- [ ] Pinned Posts
|
- [x] Pinned Posts
|
||||||
- [x] Nodeinfo
|
- [x] Nodeinfo
|
||||||
- [ ] Notifications
|
- [ ] Notifications
|
||||||
|
|
||||||
@ -31,7 +31,7 @@
|
|||||||
- [x] Update profile picture
|
- [x] Update profile picture
|
||||||
- [ ] Mark account as private (in federation manual approval is needed)
|
- [ ] Mark account as private (in federation manual approval is needed)
|
||||||
- [x] Allow custom CSS
|
- [x] Allow custom CSS
|
||||||
- [ ] Profile audio
|
- [x] Profile audio
|
||||||
- [ ] Top 8 friends
|
- [ ] Top 8 friends
|
||||||
- [x] Friends (they are known as follows in the activitypub protocol)
|
- [x] Friends (they are known as follows in the activitypub protocol)
|
||||||
- [x] Add friends
|
- [x] Add friends
|
||||||
@ -67,4 +67,4 @@
|
|||||||
- [x] Use jobs when posting activities
|
- [x] Use jobs when posting activities
|
||||||
- [ ] Sign the get activities for mastodon when secure mode is enable
|
- [ ] Sign the get activities for mastodon when secure mode is enable
|
||||||
- [x] Set a minimum font size for the tags cloud
|
- [x] Set a minimum font size for the tags cloud
|
||||||
- [ ] Pagination, pagination, AND MORE PAGINATION
|
- [x] Pagination, pagination, AND MORE PAGINATION
|
||||||
|
62
app/Events/AP/ActivityUndoEvent.php
Normal file
62
app/Events/AP/ActivityUndoEvent.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events\AP;
|
||||||
|
|
||||||
|
use App\Models\Activity;
|
||||||
|
use App\Models\Actor;
|
||||||
|
|
||||||
|
use App\Types\TypeActivity;
|
||||||
|
use App\Types\TypeActor;
|
||||||
|
|
||||||
|
use App\Events\UserUnfollowedEvent;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ActivityUndoEvent
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $activity;
|
||||||
|
public $actor;
|
||||||
|
public $object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct($activity)
|
||||||
|
{
|
||||||
|
$this->activity = $activity;
|
||||||
|
|
||||||
|
$this->actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]);
|
||||||
|
|
||||||
|
$child_activity = $activity ["object"];
|
||||||
|
$child_activity_id = "";
|
||||||
|
|
||||||
|
if (is_array ($child_activity))
|
||||||
|
$child_activity_id = $child_activity ["id"];
|
||||||
|
else
|
||||||
|
$child_activity_id = $child_activity;
|
||||||
|
|
||||||
|
if (!TypeActivity::activity_exists ($child_activity_id))
|
||||||
|
return ["error" => "Activity not found",];
|
||||||
|
|
||||||
|
$child_activity = Activity::where ("activity_id", $child_activity_id)->first ();
|
||||||
|
$this->object = $child_activity;
|
||||||
|
|
||||||
|
switch ($this->object->type)
|
||||||
|
{
|
||||||
|
case "Follow":
|
||||||
|
$unfollowed_actor = Actor::where ("actor_id", $this->object->object)->first ();
|
||||||
|
UserUnfollowedEvent::dispatch ($this->object, $this->actor, $unfollowed_actor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$child_activity->delete ();
|
||||||
|
}
|
||||||
|
}
|
34
app/Events/NoteLikedEvent.php
Normal file
34
app/Events/NoteLikedEvent.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use App\Models\Actor;
|
||||||
|
use App\Models\Activity;
|
||||||
|
use App\Models\Note;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class NoteLikedEvent
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public Activity $activity;
|
||||||
|
public Actor $actor;
|
||||||
|
public Note $note;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Activity $activity, Actor $actor, Note $note)
|
||||||
|
{
|
||||||
|
$this->activity = $activity;
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->note = $note;
|
||||||
|
}
|
||||||
|
}
|
34
app/Events/UserFollowedEvent.php
Normal file
34
app/Events/UserFollowedEvent.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use App\Models\Activity;
|
||||||
|
use App\Models\Actor;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class UserFollowedEvent
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public Activity $activity;
|
||||||
|
public Actor $actor;
|
||||||
|
public Actor $object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct($activity, $actor, $object)
|
||||||
|
{
|
||||||
|
$this->activity = $activity;
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->object = $object;
|
||||||
|
}
|
||||||
|
}
|
33
app/Events/UserUnfollowedEvent.php
Normal file
33
app/Events/UserUnfollowedEvent.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use App\Models\Activity;
|
||||||
|
use App\Models\Actor;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class UserUnfollowedEvent
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public Activity $activity;
|
||||||
|
public Actor $actor;
|
||||||
|
public Actor $object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(Activity $activity, Actor $actor, Actor $object)
|
||||||
|
{
|
||||||
|
$this->activity = $activity;
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->object = $object;
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,11 @@ use App\Models\Like;
|
|||||||
use App\Types\TypeActor;
|
use App\Types\TypeActor;
|
||||||
use App\Types\TypeActivity;
|
use App\Types\TypeActivity;
|
||||||
|
|
||||||
|
use App\Events\UserFollowedEvent;
|
||||||
|
use App\Events\NoteLikedEvent;
|
||||||
|
|
||||||
|
use App\Events\AP\ActivityUndoEvent;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@ -66,14 +71,9 @@ class APInboxController extends Controller
|
|||||||
return response ()->json (["error" => "Follow already exists",], 409);
|
return response ()->json (["error" => "Follow already exists",], 409);
|
||||||
|
|
||||||
$activity ["activity_id"] = $activity ["id"];
|
$activity ["activity_id"] = $activity ["id"];
|
||||||
|
|
||||||
$act = Activity::create ($activity);
|
$act = Activity::create ($activity);
|
||||||
|
|
||||||
$follow = Follow::create ([
|
UserFollowedEvent::dispatch ($act, $actor, $target);
|
||||||
"activity_id" => $act->id,
|
|
||||||
"actor" => $actor->id,
|
|
||||||
"object" => $target->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// TODO: Users should be able to manually check this
|
// TODO: Users should be able to manually check this
|
||||||
$accept_activity = TypeActivity::craft_accept ($act);
|
$accept_activity = TypeActivity::craft_accept ($act);
|
||||||
@ -88,6 +88,7 @@ class APInboxController extends Controller
|
|||||||
|
|
||||||
public function handle_undo (User $user, $activity)
|
public function handle_undo (User $user, $activity)
|
||||||
{
|
{
|
||||||
|
ActivityUndoEvent::dispatch ($activity, $activity);
|
||||||
return response ()->json (ActionsActivity::activity_undo ($activity));
|
return response ()->json (ActionsActivity::activity_undo ($activity));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +115,7 @@ class APInboxController extends Controller
|
|||||||
else
|
else
|
||||||
$act = Activity::where ("activity_id", $activity ["id"])->first ();
|
$act = Activity::where ("activity_id", $activity ["id"])->first ();
|
||||||
|
|
||||||
$like = Like::create ([
|
NoteLikedEvent::dispatch ($act, $actor, $note);
|
||||||
"activity_id" => $act->id,
|
|
||||||
"actor_id" => $actor->id,
|
|
||||||
"note_id" => $note->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response ()->json (["success" => "Like created",], 200);
|
return response ()->json (["success" => "Like created",], 200);
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,23 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\AP;
|
namespace App\Http\Controllers\AP;
|
||||||
|
|
||||||
use App\Actions\ActionsActivity;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
use App\Models\Actor;
|
use App\Models\Actor;
|
||||||
use App\Models\Activity;
|
use App\Models\Activity;
|
||||||
use App\Models\Announcement;
|
use App\Models\Announcement;
|
||||||
|
use App\Models\ProfilePin;
|
||||||
|
|
||||||
use App\Types\TypeActor;
|
use App\Types\TypeActor;
|
||||||
use App\Types\TypeActivity;
|
use App\Types\TypeActivity;
|
||||||
use App\Types\TypeNote;
|
use App\Types\TypeNote;
|
||||||
|
|
||||||
|
use App\Actions\ActionsActivity;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\ProfilePin;
|
|
||||||
|
use App\Notifications\UserNotification;
|
||||||
|
|
||||||
class APInstanceInboxController extends Controller
|
class APInstanceInboxController extends Controller
|
||||||
{
|
{
|
||||||
@ -93,6 +96,12 @@ class APInstanceInboxController extends Controller
|
|||||||
"note_id" => $note_exists->id
|
"note_id" => $note_exists->id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$note_actor = $note_exists->get_actor ()->first ();
|
||||||
|
if ($note_actor->user)
|
||||||
|
{
|
||||||
|
$note_actor->user->notify (new UserNotification("Boost", $announcement_actor, $note_exists));
|
||||||
|
}
|
||||||
|
|
||||||
return response ()->json (["status" => "ok"]);
|
return response ()->json (["status" => "ok"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
46
app/Listeners/NoteLikedListener.php
Normal file
46
app/Listeners/NoteLikedListener.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Listeners;
|
||||||
|
|
||||||
|
use App\Models\Like;
|
||||||
|
|
||||||
|
use App\Events\NoteLikedEvent;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Notifications\UserNotification;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
||||||
|
class NoteLikedListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the event listener.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the event.
|
||||||
|
*/
|
||||||
|
public function handle(NoteLikedEvent $event): void
|
||||||
|
{
|
||||||
|
$like = Like::create ([
|
||||||
|
"activity_id" => $event->activity->id,
|
||||||
|
"actor_id" => $event->actor->id,
|
||||||
|
"note_id" => $event->note->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $event->note->get_actor ()->first ()->user;
|
||||||
|
if (!$user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$user->notify (new UserNotification(
|
||||||
|
"Like",
|
||||||
|
$event->actor->id,
|
||||||
|
$event->note->id,
|
||||||
|
$event->activity->id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
49
app/Listeners/UserFollowedListener.php
Normal file
49
app/Listeners/UserFollowedListener.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Listeners;
|
||||||
|
|
||||||
|
use App\Events\UserFollowedEvent;
|
||||||
|
|
||||||
|
use App\Models\Follow;
|
||||||
|
use App\Models\Actor;
|
||||||
|
use App\Models\Activity;
|
||||||
|
|
||||||
|
use App\Notifications\UserNotification;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class UserFollowedListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the event listener.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the event.
|
||||||
|
*/
|
||||||
|
public function handle(UserFollowedEvent $event): void
|
||||||
|
{
|
||||||
|
Follow::create ([
|
||||||
|
"activity_id" => $event->activity->id,
|
||||||
|
"actor" => $event->actor->id,
|
||||||
|
"object" => $event->object->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $event->object->user;
|
||||||
|
if (!$user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$user->notify (new UserNotification (
|
||||||
|
"Follow",
|
||||||
|
$event->actor->id,
|
||||||
|
$event->object->id,
|
||||||
|
$event->activity->id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
48
app/Listeners/UserUnfollowedListener.php
Normal file
48
app/Listeners/UserUnfollowedListener.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Listeners;
|
||||||
|
|
||||||
|
use App\Models\Actor;
|
||||||
|
use App\Models\Follow;
|
||||||
|
|
||||||
|
use App\Notifications\UserNotification;
|
||||||
|
|
||||||
|
use App\Events\UserUnfollowedEvent;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class UserUnfollowedListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the event listener.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the event.
|
||||||
|
*/
|
||||||
|
public function handle(UserUnfollowedEvent $event): void
|
||||||
|
{
|
||||||
|
$follow_exists = Follow::where("actor_id", $event->actor->id)
|
||||||
|
->where("object_id", $event->object->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($follow_exists)
|
||||||
|
$follow_exists->delete ();
|
||||||
|
|
||||||
|
$user = $event->object->user;
|
||||||
|
if (!$user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$user->notify(new UserNotification(
|
||||||
|
"Unfollow",
|
||||||
|
$event->actor->id,
|
||||||
|
$event->object->id,
|
||||||
|
$event->activity->id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
65
app/Notifications/UserNotification.php
Normal file
65
app/Notifications/UserNotification.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class UserNotification extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $type;
|
||||||
|
public $actor;
|
||||||
|
public $object;
|
||||||
|
public $activity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*/
|
||||||
|
public function __construct($type, $actor, $object, $activity = null)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->object = $object;
|
||||||
|
$this->activity = $activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notification's delivery channels.
|
||||||
|
*
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
return ['database', 'broadcast'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the array representation of the notification.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(object $notifiable): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => $this->type,
|
||||||
|
'actor' => $this->actor,
|
||||||
|
'object' => $this->object,
|
||||||
|
'activity' => $this->activity,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toBroadcast ($notifiable)
|
||||||
|
{
|
||||||
|
// we don't really need to broadcast any information
|
||||||
|
return [
|
||||||
|
"notification_type" => $this->type,
|
||||||
|
"actor" => $this->actor,
|
||||||
|
"object" => $this->object,
|
||||||
|
"activity" => $this->activity,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ use App\Models\Actor;
|
|||||||
use App\Models\Activity;
|
use App\Models\Activity;
|
||||||
use App\Models\NoteAttachment;
|
use App\Models\NoteAttachment;
|
||||||
use App\Models\NoteMention;
|
use App\Models\NoteMention;
|
||||||
|
use App\Notifications\UserNotification;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@ -187,19 +188,23 @@ class TypeNote
|
|||||||
$mention_actor = null;
|
$mention_actor = null;
|
||||||
|
|
||||||
$actor_exists = null;
|
$actor_exists = null;
|
||||||
Log::info (json_encode ($exploded_name));
|
|
||||||
|
|
||||||
|
$is_local = false;
|
||||||
if (count ($exploded_name) == 2)
|
if (count ($exploded_name) == 2)
|
||||||
{
|
{
|
||||||
// let's check if maybe it's local
|
// let's check if maybe it's local
|
||||||
$actor_exists = Actor::where ("preferredUsername", $exploded_name [1])->first ();
|
$actor_exists = Actor::where ("preferredUsername", $exploded_name [1])->first ();
|
||||||
if (!$actor_exists)
|
if (!$actor_exists)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
$is_local = true;
|
||||||
}
|
}
|
||||||
else if (count ($exploded_name) == 3)
|
else if (count ($exploded_name) == 3)
|
||||||
{
|
{
|
||||||
// maybe it's remote
|
// maybe it's remote
|
||||||
$actor_exists = TypeActor::actor_exists_or_obtain_from_handle($exploded_name [1], $exploded_name [2]);
|
$actor_exists = TypeActor::actor_exists_or_obtain_from_handle($exploded_name [1], $exploded_name [2]);
|
||||||
|
if ($actor_exists->user)
|
||||||
|
$is_local = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
@ -208,6 +213,15 @@ class TypeNote
|
|||||||
"note_id" => $note->id,
|
"note_id" => $note->id,
|
||||||
"actor_id" => $actor_exists->id
|
"actor_id" => $actor_exists->id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($is_local)
|
||||||
|
{
|
||||||
|
$actor_exists->user->notify (new UserNotification(
|
||||||
|
"Mention",
|
||||||
|
$actor,
|
||||||
|
$actor_exists
|
||||||
|
));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
->withRouting(
|
->withRouting(
|
||||||
web: __DIR__.'/../routes/web.php',
|
web: __DIR__.'/../routes/web.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
channels: __DIR__.'/../routes/channels.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware) {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"guzzlehttp/guzzle": "^7.9",
|
"guzzlehttp/guzzle": "^7.9",
|
||||||
"intervention/image": "^3.10",
|
"intervention/image": "^3.10",
|
||||||
"laravel/framework": "^11.31",
|
"laravel/framework": "^11.31",
|
||||||
|
"laravel/reverb": "^1.0",
|
||||||
"laravel/tinker": "^2.9",
|
"laravel/tinker": "^2.9",
|
||||||
"league/html-to-markdown": "^5.1",
|
"league/html-to-markdown": "^5.1",
|
||||||
"predis/predis": "^2.3"
|
"predis/predis": "^2.3"
|
||||||
|
1000
composer.lock
generated
1000
composer.lock
generated
File diff suppressed because it is too large
Load Diff
82
config/broadcasting.php
Normal file
82
config/broadcasting.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Broadcaster
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default broadcaster that will be used by the
|
||||||
|
| framework when an event needs to be broadcast. You may set this to
|
||||||
|
| any of the connections defined in the "connections" array below.
|
||||||
|
|
|
||||||
|
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('BROADCAST_CONNECTION', 'null'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Broadcast Connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define all of the broadcast connections that will be used
|
||||||
|
| to broadcast events to other systems or over WebSockets. Samples of
|
||||||
|
| each available type of connection are provided inside this array.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
|
||||||
|
'reverb' => [
|
||||||
|
'driver' => 'reverb',
|
||||||
|
'key' => env('REVERB_APP_KEY'),
|
||||||
|
'secret' => env('REVERB_APP_SECRET'),
|
||||||
|
'app_id' => env('REVERB_APP_ID'),
|
||||||
|
'options' => [
|
||||||
|
'host' => env('REVERB_HOST'),
|
||||||
|
'port' => env('REVERB_PORT', 443),
|
||||||
|
'scheme' => env('REVERB_SCHEME', 'https'),
|
||||||
|
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
||||||
|
],
|
||||||
|
'client_options' => [
|
||||||
|
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'pusher' => [
|
||||||
|
'driver' => 'pusher',
|
||||||
|
'key' => env('PUSHER_APP_KEY'),
|
||||||
|
'secret' => env('PUSHER_APP_SECRET'),
|
||||||
|
'app_id' => env('PUSHER_APP_ID'),
|
||||||
|
'options' => [
|
||||||
|
'cluster' => env('PUSHER_APP_CLUSTER'),
|
||||||
|
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||||
|
'port' => env('PUSHER_PORT', 443),
|
||||||
|
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||||
|
'encrypted' => true,
|
||||||
|
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||||
|
],
|
||||||
|
'client_options' => [
|
||||||
|
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'ably' => [
|
||||||
|
'driver' => 'ably',
|
||||||
|
'key' => env('ABLY_KEY'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'log' => [
|
||||||
|
'driver' => 'log',
|
||||||
|
],
|
||||||
|
|
||||||
|
'null' => [
|
||||||
|
'driver' => 'null',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
92
config/reverb.php
Normal file
92
config/reverb.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Reverb Server
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default server used by Reverb to handle
|
||||||
|
| incoming messages as well as broadcasting message to all your
|
||||||
|
| connected clients. At this time only "reverb" is supported.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('REVERB_SERVER', 'reverb'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Reverb Servers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define details for each of the supported Reverb servers.
|
||||||
|
| Each server has its own configuration options that are defined in
|
||||||
|
| the array below. You should ensure all the options are present.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'servers' => [
|
||||||
|
|
||||||
|
'reverb' => [
|
||||||
|
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
|
||||||
|
'port' => env('REVERB_SERVER_PORT', 8080),
|
||||||
|
'hostname' => env('REVERB_HOST'),
|
||||||
|
'options' => [
|
||||||
|
'tls' => [],
|
||||||
|
],
|
||||||
|
'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
|
||||||
|
'scaling' => [
|
||||||
|
'enabled' => env('REVERB_SCALING_ENABLED', false),
|
||||||
|
'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
|
||||||
|
'server' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'database' => env('REDIS_DB', '0'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
|
||||||
|
'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Reverb Applications
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define how Reverb applications are managed. If you choose
|
||||||
|
| to use the "config" provider, you may define an array of apps which
|
||||||
|
| your server will support, including their connection credentials.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'apps' => [
|
||||||
|
|
||||||
|
'provider' => 'config',
|
||||||
|
|
||||||
|
'apps' => [
|
||||||
|
[
|
||||||
|
'key' => env('REVERB_APP_KEY'),
|
||||||
|
'secret' => env('REVERB_APP_SECRET'),
|
||||||
|
'app_id' => env('REVERB_APP_ID'),
|
||||||
|
'options' => [
|
||||||
|
'host' => env('REVERB_HOST'),
|
||||||
|
'port' => env('REVERB_PORT', 443),
|
||||||
|
'scheme' => env('REVERB_SCHEME', 'https'),
|
||||||
|
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
||||||
|
],
|
||||||
|
'allowed_origins' => ['*'],
|
||||||
|
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
|
||||||
|
'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
|
||||||
|
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('notifications', function (Blueprint $table) {
|
||||||
|
$table->uuid('id')->primary();
|
||||||
|
$table->string('type');
|
||||||
|
$table->morphs('notifiable');
|
||||||
|
$table->text('data');
|
||||||
|
$table->timestamp('read_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('notifications');
|
||||||
|
}
|
||||||
|
};
|
26
package-lock.json
generated
26
package-lock.json
generated
@ -8,8 +8,10 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
|
"laravel-echo": "^1.17.1",
|
||||||
"laravel-vite-plugin": "^1.0",
|
"laravel-vite-plugin": "^1.0",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
|
"pusher-js": "^8.4.0-rc2",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
"vite": "^6.0"
|
"vite": "^6.0"
|
||||||
}
|
}
|
||||||
@ -1585,6 +1587,15 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/laravel-echo": {
|
||||||
|
"version": "1.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.17.1.tgz",
|
||||||
|
"integrity": "sha512-ORWc4vDfnBj/Oe5ThZ5kYyGItRjLDqAQUyhD/7UhehUOqc+s5x9HEBjtMVludNMP6VuXw6t7Uxt8bp63kaTofg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/laravel-vite-plugin": {
|
"node_modules/laravel-vite-plugin": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.1.1.tgz",
|
||||||
@ -1994,6 +2005,15 @@
|
|||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/pusher-js": {
|
||||||
|
"version": "8.4.0-rc2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz",
|
||||||
|
"integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tweetnacl": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -2434,6 +2454,12 @@
|
|||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/tweetnacl": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||||
|
@ -9,8 +9,10 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
|
"laravel-echo": "^1.17.1",
|
||||||
"laravel-vite-plugin": "^1.0",
|
"laravel-vite-plugin": "^1.0",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
|
"pusher-js": "^8.4.0-rc2",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
"vite": "^6.0"
|
"vite": "^6.0"
|
||||||
}
|
}
|
||||||
|
BIN
public/resources/sounds/notification.mp3
Normal file
BIN
public/resources/sounds/notification.mp3
Normal file
Binary file not shown.
8
resources/js/bootstrap.js
vendored
8
resources/js/bootstrap.js
vendored
@ -15,3 +15,11 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo exposes an expressive API for subscribing to channels and listening
|
||||||
|
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||||
|
* allow your team to quickly build robust real-time web applications.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import './echo';
|
||||||
|
14
resources/js/echo.js
Normal file
14
resources/js/echo.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Echo from 'laravel-echo';
|
||||||
|
|
||||||
|
import Pusher from 'pusher-js';
|
||||||
|
window.Pusher = Pusher;
|
||||||
|
|
||||||
|
window.Echo = new Echo({
|
||||||
|
broadcaster: 'reverb',
|
||||||
|
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||||
|
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||||
|
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
||||||
|
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
||||||
|
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
||||||
|
enabledTransports: ['ws', 'wss'],
|
||||||
|
});
|
@ -41,5 +41,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@vite(["resources/js/app.js"])
|
@vite(["resources/js/app.js"])
|
||||||
|
|
||||||
|
@if (auth ()->check ())
|
||||||
|
<script>
|
||||||
|
const notification_sound = new Audio ("/resources/sounds/notification.mp3")
|
||||||
|
notification_sound.muted = true
|
||||||
|
notification_sound.play ()
|
||||||
|
notification_sound.muted = false
|
||||||
|
|
||||||
|
function register_echo ()
|
||||||
|
{
|
||||||
|
window.Echo.private ("App.Models.User.{{ auth ()->id () }}")
|
||||||
|
.notification ((notification) =>
|
||||||
|
{
|
||||||
|
notification_sound.play ()
|
||||||
|
|
||||||
|
// of course, remove this
|
||||||
|
alert ("You received a " + notification.notification_type + " notification!")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener ("DOMContentLoaded", register_echo)
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
7
routes/channels.php
Normal file
7
routes/channels.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
|
|
||||||
|
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
|
||||||
|
return (int) $user->id === (int) $id;
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user