206 lines
7.9 KiB
PHP
206 lines
7.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Notification;
|
|
use App\Models\User;
|
|
use App\Support\AvatarUrl;
|
|
use Illuminate\Support\Collection;
|
|
|
|
final class NotificationService
|
|
{
|
|
public function notifyUserFollowed(User $recipient, User $actor): ?Notification
|
|
{
|
|
if ($recipient->id === $actor->id) {
|
|
return null;
|
|
}
|
|
|
|
if ($recipient->profile && $recipient->profile->follower_notifications === false) {
|
|
return null;
|
|
}
|
|
|
|
$label = $actor->name ?: $actor->username ?: 'Someone';
|
|
|
|
return Notification::query()->create([
|
|
'user_id' => (int) $recipient->id,
|
|
'type' => 'user_followed',
|
|
'data' => [
|
|
'type' => 'user_followed',
|
|
'actor_id' => (int) $actor->id,
|
|
'actor_name' => $actor->name,
|
|
'actor_username' => $actor->username,
|
|
'message' => $label . ' started following you',
|
|
'url' => $actor->username ? '/@' . $actor->username : null,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function notifyCollectionInvite(User $recipient, User $actor, \App\Models\Collection $collection, string $role): ?Notification
|
|
{
|
|
if ($recipient->id === $actor->id) {
|
|
return null;
|
|
}
|
|
|
|
return Notification::query()->create([
|
|
'user_id' => (int) $recipient->id,
|
|
'type' => 'collection_invite',
|
|
'data' => [
|
|
'type' => 'collection_invite',
|
|
'actor_id' => (int) $actor->id,
|
|
'actor_name' => $actor->name,
|
|
'actor_username' => $actor->username,
|
|
'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' invited you to collaborate on ' . $collection->title,
|
|
'url' => route('settings.collections.show', ['collection' => $collection->id]),
|
|
'collection_id' => (int) $collection->id,
|
|
'collection_title' => (string) $collection->title,
|
|
'role' => $role,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function notifyCollectionSubmission(User $recipient, User $actor, \App\Models\Collection $collection, \App\Models\Artwork $artwork): ?Notification
|
|
{
|
|
if ($recipient->id === $actor->id) {
|
|
return null;
|
|
}
|
|
|
|
return Notification::query()->create([
|
|
'user_id' => (int) $recipient->id,
|
|
'type' => 'collection_submission',
|
|
'data' => [
|
|
'type' => 'collection_submission',
|
|
'actor_id' => (int) $actor->id,
|
|
'actor_name' => $actor->name,
|
|
'actor_username' => $actor->username,
|
|
'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' submitted "' . $artwork->title . '" to ' . $collection->title,
|
|
'url' => route('settings.collections.show', ['collection' => $collection->id]),
|
|
'collection_id' => (int) $collection->id,
|
|
'artwork_id' => (int) $artwork->id,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function notifyCollectionComment(User $recipient, User $actor, \App\Models\Collection $collection, \App\Models\CollectionComment $comment): ?Notification
|
|
{
|
|
if ($recipient->id === $actor->id) {
|
|
return null;
|
|
}
|
|
|
|
return Notification::query()->create([
|
|
'user_id' => (int) $recipient->id,
|
|
'type' => 'collection_comment',
|
|
'data' => [
|
|
'type' => 'collection_comment',
|
|
'actor_id' => (int) $actor->id,
|
|
'actor_name' => $actor->name,
|
|
'actor_username' => $actor->username,
|
|
'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' commented on ' . $collection->title,
|
|
'url' => route('profile.collections.show', ['username' => strtolower((string) $collection->user->username), 'slug' => $collection->slug]),
|
|
'collection_id' => (int) $collection->id,
|
|
'comment_id' => (int) $comment->id,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function notifyNovaCardComment(User $recipient, User $actor, \App\Models\NovaCard $card, \App\Models\NovaCardComment $comment): ?Notification
|
|
{
|
|
if ($recipient->id === $actor->id) {
|
|
return null;
|
|
}
|
|
|
|
return Notification::query()->create([
|
|
'user_id' => (int) $recipient->id,
|
|
'type' => 'nova_card_comment',
|
|
'data' => [
|
|
'type' => 'nova_card_comment',
|
|
'actor_id' => (int) $actor->id,
|
|
'actor_name' => $actor->name,
|
|
'actor_username' => $actor->username,
|
|
'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' commented on ' . $card->title,
|
|
'url' => $card->publicUrl() . '#comment-' . $comment->id,
|
|
'card_id' => (int) $card->id,
|
|
'comment_id' => (int) $comment->id,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function listForUser(User $user, int $page = 1, int $perPage = 20): array
|
|
{
|
|
$resolvedPage = max(1, $page);
|
|
$resolvedPerPage = max(1, min(50, $perPage));
|
|
|
|
$notifications = $user->notifications()
|
|
->latest()
|
|
->paginate($resolvedPerPage, ['*'], 'page', $resolvedPage);
|
|
|
|
$actorIds = collect($notifications->items())
|
|
->map(function (Notification $notification): ?int {
|
|
$data = is_array($notification->data) ? $notification->data : [];
|
|
|
|
return isset($data['actor_id']) ? (int) $data['actor_id'] : null;
|
|
})
|
|
->filter()
|
|
->unique()
|
|
->values();
|
|
|
|
$actors = $actorIds->isEmpty()
|
|
? collect()
|
|
: User::query()
|
|
->with('profile:user_id,avatar_hash')
|
|
->whereIn('id', $actorIds->all())
|
|
->get()
|
|
->keyBy('id');
|
|
|
|
return [
|
|
'data' => collect($notifications->items())
|
|
->map(fn (Notification $notification) => $this->mapNotification($notification, $actors))
|
|
->values()
|
|
->all(),
|
|
'unread_count' => $user->unreadNotifications()->count(),
|
|
'meta' => [
|
|
'total' => $notifications->total(),
|
|
'current_page' => $notifications->currentPage(),
|
|
'last_page' => $notifications->lastPage(),
|
|
'per_page' => $notifications->perPage(),
|
|
],
|
|
];
|
|
}
|
|
|
|
public function markAllRead(User $user): void
|
|
{
|
|
$user->unreadNotifications()->update(['read_at' => now()]);
|
|
}
|
|
|
|
public function markRead(User $user, string $id): void
|
|
{
|
|
$notification = $user->notifications()->findOrFail($id);
|
|
$notification->markAsRead();
|
|
}
|
|
|
|
private function mapNotification(Notification $notification, Collection $actors): array
|
|
{
|
|
$data = is_array($notification->data) ? $notification->data : [];
|
|
$actorId = isset($data['actor_id']) ? (int) $data['actor_id'] : null;
|
|
$actor = $actorId ? $actors->get($actorId) : null;
|
|
|
|
return [
|
|
'id' => (string) $notification->id,
|
|
'type' => (string) ($data['type'] ?? $notification->type ?? 'notification'),
|
|
'message' => (string) ($data['message'] ?? 'New activity'),
|
|
'url' => $data['url'] ?? null,
|
|
'created_at' => $notification->created_at?->toIso8601String(),
|
|
'time_ago' => $notification->created_at?->diffForHumans(),
|
|
'read' => $notification->read_at !== null,
|
|
'actor' => $actor ? [
|
|
'id' => (int) $actor->id,
|
|
'name' => $actor->name,
|
|
'username' => $actor->username,
|
|
'avatar_url' => AvatarUrl::forUser((int) $actor->id, $actor->profile?->avatar_hash, 64),
|
|
'profile_url' => $actor->username ? '/@' . $actor->username : null,
|
|
] : null,
|
|
];
|
|
}
|
|
}
|