the browser-facing portion of osu!
at master 116 lines 3.4 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace App\Jobs; 7 8use App\Exceptions\InvalidNotificationException; 9use App\Jobs\Notifications\BroadcastNotificationBase; 10use App\Mail\UserNotificationDigest as UserNotificationDigestMail; 11use App\Models\Notification; 12use App\Models\User; 13use App\Models\UserNotification; 14use DB; 15use Illuminate\Bus\Queueable; 16use Illuminate\Contracts\Queue\ShouldQueue; 17use Illuminate\Queue\SerializesModels; 18use Illuminate\Support\Collection; 19use Mail; 20 21class UserNotificationDigest implements ShouldQueue 22{ 23 use Queueable, SerializesModels; 24 25 private $fromId; 26 private $now; 27 private $toId; 28 private $user; 29 private $watches; 30 31 public function __construct(User $user, int $fromId, int $toId) 32 { 33 $this->user = $user; 34 $this->fromId = $fromId; 35 $this->toId = $toId; 36 $this->now = now(); 37 } 38 39 public function handle() 40 { 41 if (!is_valid_email_format($this->user->user_email)) { 42 return; 43 } 44 45 $notifications = $this->filterNotifications($this->getNotifications()); 46 47 if (empty($notifications)) { 48 return; 49 } 50 51 // TODO: catch and log errors? 52 Mail::to($this->user)->send(new UserNotificationDigestMail($notifications, $this->user)); 53 54 datadog_increment('user_notification_digest.mail'); 55 } 56 57 private function filterNotifications(Collection $notifications) 58 { 59 // preload the watches and key them for lookup. 60 $this->watches = [ 61 'topics' => $this->user->topicWatches() 62 ->whereIn('topic_id', $notifications->where('notifiable_type', '=', 'forum_topic')->pluck('notifiable_id')) 63 ->where('mail', true) 64 ->where('notify_status', false) 65 ->get() 66 ->keyBy('topic_id'), 67 ]; 68 69 $filtered = []; 70 71 foreach ($notifications as $notification) { 72 if (!$this->shouldSend($notification)) { 73 continue; 74 } 75 76 $filtered[] = $notification; 77 } 78 79 // bulk update the watches 80 DB::transaction(function () { 81 $topicIds = $this->watches['topics']->filter(function ($watch) { 82 return $watch->isDirty(); 83 })->keys(); 84 85 $this->user->topicWatches()->whereIn('topic_id', $topicIds)->update(['notify_status' => true]); 86 }); 87 88 return $filtered; 89 } 90 91 private function getNotifications() 92 { 93 $notificationIdsQuery = UserNotification 94 ::where('user_id', $this->user->getKey()) 95 ->where('is_read', false) 96 ->hasMailDelivery() 97 ->where('id', '>', $this->fromId) 98 ->where('id', '<=', $this->toId) 99 ->select('notification_id'); 100 101 return Notification::whereIn('id', $notificationIdsQuery)->get(); 102 } 103 104 private function shouldSend(Notification $notification): bool 105 { 106 try { 107 $class = BroadcastNotificationBase::getNotificationClassFromNotification($notification); 108 109 return $class::shouldSendMail($notification, $this->watches, $this->now); 110 } catch (InvalidNotificationException $e) { 111 log_error($e); 112 113 return false; 114 } 115 } 116}