the browser-facing portion of osu!
at master 4.6 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\Models; 7 8use App\Events\NotificationDeleteEvent; 9use App\Events\NotificationReadEvent; 10use App\Libraries\Notification\BatchIdentities; 11use DB; 12use Illuminate\Database\Eloquent\Builder; 13 14class UserNotification extends Model 15{ 16 const DELIVERY_OFFSETS = [ 17 'push' => 0, 18 'mail' => 1, 19 ]; 20 21 protected $casts = [ 22 'is_read' => 'boolean', 23 ]; 24 25 public static function batchDestroy(int $userId, BatchIdentities $batchIdentities) 26 { 27 $notificationIds = $batchIdentities->getNotificationIds(); 28 $identities = $batchIdentities->getIdentities(); 29 30 if (empty($notificationIds)) { 31 return; 32 } 33 34 $now = now(); 35 $userNotificationQuery = static::where(['user_id' => $userId]); 36 // obtain and filter valid user notification ids 37 $ids = $userNotificationQuery->clone() 38 ->whereIn('notification_id', $notificationIds) 39 ->where('created_at', '<=', $now) 40 ->pluck('id') 41 ->all(); 42 43 if (empty($ids)) { 44 return; 45 } 46 47 $readCount = 0; 48 49 foreach (array_chunk($ids, 1000) as $chunkedIds) { 50 $unreadCountQuery = $userNotificationQuery->clone() 51 ->hasPushDelivery() 52 ->where('is_read', false) 53 ->whereIn('id', $chunkedIds); 54 $unreadCountInitial = $unreadCountQuery->count(); 55 $userNotificationQuery->clone() 56 ->whereIn('id', $chunkedIds) 57 ->delete(); 58 59 $unreadCountCurrent = $unreadCountQuery->count(); 60 $readCount += $unreadCountInitial - $unreadCountCurrent; 61 } 62 63 (new NotificationDeleteEvent($userId, [ 64 'notifications' => $identities, 65 'read_count' => $readCount, 66 'timestamp' => $now, 67 ]))->broadcast(); 68 } 69 70 public static function batchMarkAsRead(User $user, BatchIdentities $batchIdentities) 71 { 72 $ids = $batchIdentities->getNotificationIds(); 73 $identities = $batchIdentities->getIdentities(); 74 $now = now(); 75 76 if (empty($ids)) { 77 return; 78 } else if ($ids instanceof Builder) { 79 $instance = new static(); 80 $tableName = $instance->getTable(); 81 // force mysql optimizer to optimize properly with a fake multi-table update 82 // https://dev.mysql.com/doc/refman/8.0/en/subquery-optimization.html 83 // FIXME: this is supposedly fixed by mysql 8.0.22 84 $itemsQuery = $instance->getConnection() 85 ->table(DB::raw("{$tableName}, (SELECT 1) dummy")) 86 ->where('user_id', $user->getKey()) 87 ->where('is_read', false) 88 ->whereIn('notification_id', $ids); 89 // raw builder doesn't have model scope magic. 90 $instance->scopeHasPushDelivery($itemsQuery); 91 92 $count = $itemsQuery->update(['is_read' => true, 'updated_at' => $now]); 93 } else { 94 $count = $user 95 ->userNotifications() 96 ->hasPushDelivery() 97 ->where('is_read', false) 98 ->whereIn('notification_id', $ids) 99 ->update(['is_read' => true, 'updated_at' => $now]); 100 } 101 102 if ($count > 0) { 103 (new NotificationReadEvent($user->getKey(), ['notifications' => $identities, 'read_count' => $count, 'timestamp' => $now]))->broadcast(); 104 } 105 } 106 107 public static function deliveryMask(string $type): int 108 { 109 return 1 << self::DELIVERY_OFFSETS[$type]; 110 } 111 112 public function isDelivery(string $type): bool 113 { 114 $mask = static::deliveryMask($type); 115 116 return ($this->delivery & $mask) === $mask; 117 } 118 119 public function isMail(): bool 120 { 121 return $this->isDelivery('mail'); 122 } 123 124 public function isPush(): bool 125 { 126 return $this->isDelivery('push'); 127 } 128 129 public function notification() 130 { 131 return $this->belongsTo(Notification::class); 132 } 133 134 public function scopeHasMailDelivery($query) 135 { 136 return $query->where('delivery', '&', static::deliveryMask('mail')); 137 } 138 139 public function scopeHasPushDelivery($query) 140 { 141 return $query->where('delivery', '&', static::deliveryMask('push')); 142 } 143 144 public function user() 145 { 146 return $this->belongsTo(User::class, 'user_id'); 147 } 148}