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 notifyGroupInvite(User $recipient, User $actor, \App\Models\Group $group, string $role, ?GroupInvitation $invitation = null): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_invite', 'data' => [ 'type' => 'group_invite', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' invited you to join ' . $group->name, 'url' => route('studio.groups.index'), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'role' => $role, 'invitation_token' => $invitation?->token, 'accept_url' => $invitation ? route('studio.groups.invitations.accept', ['invitation' => $invitation]) : null, ], ]); } public function notifyGroupInviteAccepted(User $recipient, User $actor, \App\Models\Group $group): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_invite_accepted', 'data' => [ 'type' => 'group_invite_accepted', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' accepted your invite to ' . $group->name, 'url' => route('studio.groups.members', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, ], ]); } public function notifyGroupRoleChanged(User $recipient, User $actor, \App\Models\Group $group, string $role): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_role_changed', 'data' => [ 'type' => 'group_role_changed', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' changed your role in ' . $group->name . ' to ' . $role, 'url' => route('studio.groups.members', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'role' => $role, ], ]); } public function notifyGroupMemberRemoved(User $recipient, User $actor, \App\Models\Group $group): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_member_removed', 'data' => [ 'type' => 'group_member_removed', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' removed you from ' . $group->name, 'url' => route('studio.groups.index'), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, ], ]); } 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 notifyGroupJoinRequestReceived(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupJoinRequest $request): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_join_request_received', 'data' => [ 'type' => 'group_join_request_received', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' requested to join ' . $group->name, 'url' => route('studio.groups.join-requests', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'join_request_id' => (int) $request->id, ], ]); } public function notifyGroupJoinRequestApproved(User $recipient, User $actor, \App\Models\Group $group, string $role, \App\Models\GroupJoinRequest $request): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_join_request_approved', 'data' => [ 'type' => 'group_join_request_approved', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' approved your request to join ' . $group->name, 'url' => route('studio.groups.show', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'role' => $role, 'join_request_id' => (int) $request->id, ], ]); } public function notifyGroupJoinRequestRejected(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupJoinRequest $request): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_join_request_rejected', 'data' => [ 'type' => 'group_join_request_rejected', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' declined your request to join ' . $group->name, 'url' => route('groups.show', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'join_request_id' => (int) $request->id, ], ]); } public function notifyGroupArtworkSubmittedForReview(User $recipient, User $actor, \App\Models\Group $group, \App\Models\Artwork $artwork): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_artwork_submitted_for_review', 'data' => [ 'type' => 'group_artwork_submitted_for_review', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' submitted "' . $artwork->title . '" for review in ' . $group->name, 'url' => route('studio.groups.review', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'artwork_id' => (int) $artwork->id, ], ]); } public function notifyGroupArtworkApproved(User $recipient, User $actor, \App\Models\Group $group, \App\Models\Artwork $artwork): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_artwork_approved', 'data' => [ 'type' => 'group_artwork_approved', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' approved your group submission "' . $artwork->title . '".', 'url' => route('art.show', ['id' => $artwork->id, 'slug' => $artwork->slug ?: $artwork->id]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'artwork_id' => (int) $artwork->id, ], ]); } public function notifyGroupArtworkNeedsChanges(User $recipient, User $actor, \App\Models\Group $group, \App\Models\Artwork $artwork): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_artwork_needs_changes', 'data' => [ 'type' => 'group_artwork_needs_changes', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' requested changes for your group submission "' . $artwork->title . '".', 'url' => route('studio.artworks.edit', ['id' => $artwork->id]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'artwork_id' => (int) $artwork->id, ], ]); } public function notifyGroupArtworkRejected(User $recipient, User $actor, \App\Models\Group $group, \App\Models\Artwork $artwork): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_artwork_rejected', 'data' => [ 'type' => 'group_artwork_rejected', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' rejected your group submission "' . $artwork->title . '".', 'url' => route('studio.artworks.edit', ['id' => $artwork->id]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'artwork_id' => (int) $artwork->id, ], ]); } public function notifyGroupPostPublished(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupPost $post): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_post_published', 'data' => [ 'type' => 'group_post_published', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' published a new post: ' . $post->title, 'url' => route('groups.posts.show', ['group' => $group, 'post' => $post]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_post_id' => (int) $post->id, ], ]); } public function notifyGroupRecruitmentUpdated(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupRecruitmentProfile $profile): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_recruitment_updated', 'data' => [ 'type' => 'group_recruitment_updated', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' updated its recruitment profile' . ($profile->headline ? ': ' . $profile->headline : '.'), 'url' => route('groups.show', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'recruitment_profile_id' => (int) $profile->id, ], ]); } public function notifyGroupProjectReleased(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupProject $project): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_project_released', 'data' => [ 'type' => 'group_project_released', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' released a project: ' . $project->title, 'url' => route('groups.projects.show', ['group' => $group, 'project' => $project]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_project_id' => (int) $project->id, ], ]); } public function notifyGroupProjectStatusChanged(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupProject $project): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_project_status_changed', 'data' => [ 'type' => 'group_project_status_changed', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' updated project status for ' . $project->title . ' to ' . $project->status, 'url' => route('groups.projects.show', ['group' => $group, 'project' => $project]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_project_id' => (int) $project->id, ], ]); } public function notifyGroupReleaseStageChanged(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupRelease $release): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_release_stage_changed', 'data' => [ 'type' => 'group_release_stage_changed', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' moved release ' . $release->title . ' to ' . str_replace('_', ' ', $release->current_stage), 'url' => route('groups.releases.show', ['group' => $group, 'release' => $release]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_release_id' => (int) $release->id, ], ]); } public function notifyGroupReleasePublished(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupRelease $release): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_release_published', 'data' => [ 'type' => 'group_release_published', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' published a release: ' . $release->title, 'url' => route('groups.releases.show', ['group' => $group, 'release' => $release]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_release_id' => (int) $release->id, ], ]); } public function notifyGroupReleaseContributorAdded(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupRelease $release, ?string $roleLabel = null): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_release_contributor_added', 'data' => [ 'type' => 'group_release_contributor_added', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' added you to the release ' . $release->title . ($roleLabel ? ' as ' . $roleLabel : ''), 'url' => route('groups.releases.show', ['group' => $group, 'release' => $release]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_release_id' => (int) $release->id, 'role_label' => $roleLabel, ], ]); } public function notifyGroupReleaseScheduled(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupRelease $release): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } $scheduledFor = $release->planned_release_at?->format('M j, Y'); return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_release_scheduled', 'data' => [ 'type' => 'group_release_scheduled', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' scheduled the release ' . $release->title . ($scheduledFor ? ' for ' . $scheduledFor : ''), 'url' => route('groups.releases.show', ['group' => $group, 'release' => $release]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_release_id' => (int) $release->id, 'planned_release_at' => $release->planned_release_at?->toISOString(), ], ]); } public function notifyGroupMilestoneAssigned(User $recipient, User $actor, \App\Models\Group $group, string $contextType, string $contextTitle, string $milestoneTitle, string $url): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_milestone_assigned', 'data' => [ 'type' => 'group_milestone_assigned', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' assigned you the milestone ' . $milestoneTitle . ' in ' . $contextType . ' ' . $contextTitle, 'url' => $url, 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'context_type' => $contextType, 'context_title' => $contextTitle, 'milestone_title' => $milestoneTitle, ], ]); } public function notifyGroupMilestoneDueSoon(User $recipient, User $actor, \App\Models\Group $group, string $contextType, string $contextTitle, string $milestoneTitle, ?string $dueDate, string $url): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_milestone_due_soon', 'data' => [ 'type' => 'group_milestone_due_soon', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => 'The milestone ' . $milestoneTitle . ' in ' . $contextType . ' ' . $contextTitle . ' is due soon' . ($dueDate ? ' on ' . $dueDate : ''), 'url' => $url, 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'context_type' => $contextType, 'context_title' => $contextTitle, 'milestone_title' => $milestoneTitle, 'due_date' => $dueDate, ], ]); } public function notifyGroupBadgeEarned(User $recipient, \App\Models\Group $group, string $badgeLabel, ?string $url = null): ?Notification { return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_badge_earned', 'data' => [ 'type' => 'group_badge_earned', 'message' => ($group->name ?: 'Your group') . ' earned the badge ' . $badgeLabel, 'url' => $url ?: route('groups.show', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'badge_label' => $badgeLabel, ], ]); } public function notifyGroupMemberBadgeEarned(User $recipient, \App\Models\Group $group, string $badgeLabel, ?string $url = null): ?Notification { return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_member_badge_earned', 'data' => [ 'type' => 'group_member_badge_earned', 'message' => 'You earned the badge ' . $badgeLabel . ' in ' . ($group->name ?: 'a group'), 'url' => $url ?: route('groups.show', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'badge_label' => $badgeLabel, ], ]); } public function notifyFeaturedReleasePromoted(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupRelease $release): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'featured_group_release_promoted', 'data' => [ 'type' => 'featured_group_release_promoted', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' featured the release ' . $release->title, 'url' => route('groups.releases.show', ['group' => $group, 'release' => $release]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_release_id' => (int) $release->id, ], ]); } public function notifyGroupChallengePublished(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupChallenge $challenge): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_challenge_published', 'data' => [ 'type' => 'group_challenge_published', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' launched a challenge: ' . $challenge->title, 'url' => route('groups.challenges.show', ['group' => $group, 'challenge' => $challenge]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_challenge_id' => (int) $challenge->id, ], ]); } public function notifyGroupEventPublished(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupEvent $event): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_event_published', 'data' => [ 'type' => 'group_event_published', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' announced an event: ' . $event->title, 'url' => route('groups.events.show', ['group' => $group, 'event' => $event]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_event_id' => (int) $event->id, ], ]); } public function notifyGroupEventUpdated(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupEvent $event): ?Notification { if ($recipient->id === $actor->id) { return null; } if ($recipient->profile && $recipient->profile->follower_notifications === false) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_event_updated', 'data' => [ 'type' => 'group_event_updated', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($group->name ?: 'A group') . ' updated an event: ' . $event->title, 'url' => route('groups.events.show', ['group' => $group, 'event' => $event]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_event_id' => (int) $event->id, ], ]); } public function notifyGroupAssetApproved(User $recipient, User $actor, \App\Models\Group $group, \App\Models\GroupAsset $asset): ?Notification { if ($recipient->id === $actor->id) { return null; } return Notification::query()->create([ 'user_id' => (int) $recipient->id, 'type' => 'group_asset_approved', 'data' => [ 'type' => 'group_asset_approved', 'actor_id' => (int) $actor->id, 'actor_name' => $actor->name, 'actor_username' => $actor->username, 'message' => ($actor->name ?: $actor->username ?: 'Someone') . ' approved your group asset ' . $asset->title, 'url' => route('studio.groups.assets.index', ['group' => $group]), 'group_slug' => (string) $group->slug, 'group_name' => (string) $group->name, 'group_asset_id' => (int) $asset->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, ]; } }