1
0

NotificationService.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. namespace App\Services;
  3. use Cache;
  4. use Illuminate\Support\Facades\Redis;
  5. use App\{
  6. Notification,
  7. Profile
  8. };
  9. use App\Transformer\Api\NotificationTransformer;
  10. use League\Fractal;
  11. use League\Fractal\Serializer\ArraySerializer;
  12. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  13. use App\Jobs\InternalPipeline\NotificationEpochUpdatePipeline;
  14. class NotificationService {
  15. const CACHE_KEY = 'pf:services:notifications:ids:';
  16. const EPOCH_CACHE_KEY = 'pf:services:notifications:epoch-id:by-months:';
  17. const ITEM_CACHE_TTL = 86400;
  18. const MASTODON_TYPES = [
  19. 'follow',
  20. 'follow_request',
  21. 'mention',
  22. 'reblog',
  23. 'favourite',
  24. 'poll',
  25. 'status'
  26. ];
  27. public static function get($id, $start = 0, $stop = 400)
  28. {
  29. $res = collect([]);
  30. $key = self::CACHE_KEY . $id;
  31. $stop = $stop > 400 ? 400 : $stop;
  32. $ids = Redis::zrangebyscore($key, $start, $stop);
  33. if(empty($ids)) {
  34. $ids = self::coldGet($id, $start, $stop);
  35. }
  36. foreach($ids as $id) {
  37. $n = self::getNotification($id);
  38. if($n != null) {
  39. $res->push($n);
  40. }
  41. }
  42. return $res;
  43. }
  44. public static function getEpochId($months = 6)
  45. {
  46. $epoch = Cache::get(self::EPOCH_CACHE_KEY . $months);
  47. if(!$epoch) {
  48. NotificationEpochUpdatePipeline::dispatch();
  49. return 1;
  50. }
  51. return $epoch;
  52. }
  53. public static function coldGet($id, $start = 0, $stop = 400)
  54. {
  55. $stop = $stop > 400 ? 400 : $stop;
  56. $ids = Notification::where('id', '>', self::getEpochId())
  57. ->where('profile_id', $id)
  58. ->orderByDesc('id')
  59. ->skip($start)
  60. ->take($stop)
  61. ->pluck('id');
  62. foreach($ids as $key) {
  63. self::set($id, $key);
  64. }
  65. return $ids;
  66. }
  67. public static function getMax($id = false, $start = 0, $limit = 10)
  68. {
  69. $ids = self::getRankedMaxId($id, $start, $limit);
  70. if(empty($ids)) {
  71. return [];
  72. }
  73. $res = collect([]);
  74. foreach($ids as $id) {
  75. $n = self::getNotification($id);
  76. if($n != null) {
  77. $res->push($n);
  78. }
  79. }
  80. return $res->toArray();
  81. }
  82. public static function getMin($id = false, $start = 0, $limit = 10)
  83. {
  84. $ids = self::getRankedMinId($id, $start, $limit);
  85. if(empty($ids)) {
  86. return [];
  87. }
  88. $res = collect([]);
  89. foreach($ids as $id) {
  90. $n = self::getNotification($id);
  91. if($n != null) {
  92. $res->push($n);
  93. }
  94. }
  95. return $res->toArray();
  96. }
  97. public static function getMaxMastodon($id = false, $start = 0, $limit = 10)
  98. {
  99. $ids = self::getRankedMaxId($id, $start, $limit);
  100. if(empty($ids)) {
  101. return [];
  102. }
  103. $res = collect([]);
  104. foreach($ids as $id) {
  105. $n = self::rewriteMastodonTypes(self::getNotification($id));
  106. if($n != null && in_array($n['type'], self::MASTODON_TYPES)) {
  107. if(isset($n['account'])) {
  108. $n['account'] = AccountService::getMastodon($n['account']['id']);
  109. }
  110. if(isset($n['relationship'])) {
  111. unset($n['relationship']);
  112. }
  113. if(isset($n['status'])) {
  114. $n['status'] = StatusService::getMastodon($n['status']['id'], false);
  115. }
  116. $res->push($n);
  117. }
  118. }
  119. return $res->toArray();
  120. }
  121. public static function getMinMastodon($id = false, $start = 0, $limit = 10)
  122. {
  123. $ids = self::getRankedMinId($id, $start, $limit);
  124. if(empty($ids)) {
  125. return [];
  126. }
  127. $res = collect([]);
  128. foreach($ids as $id) {
  129. $n = self::rewriteMastodonTypes(self::getNotification($id));
  130. if($n != null && in_array($n['type'], self::MASTODON_TYPES)) {
  131. if(isset($n['account'])) {
  132. $n['account'] = AccountService::getMastodon($n['account']['id']);
  133. }
  134. if(isset($n['relationship'])) {
  135. unset($n['relationship']);
  136. }
  137. if(isset($n['status'])) {
  138. $n['status'] = StatusService::getMastodon($n['status']['id'], false);
  139. }
  140. $res->push($n);
  141. }
  142. }
  143. return $res->toArray();
  144. }
  145. public static function getRankedMaxId($id = false, $start = null, $limit = 10)
  146. {
  147. if(!$start || !$id) {
  148. return [];
  149. }
  150. return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY.$id, $start, '-inf', [
  151. 'withscores' => true,
  152. 'limit' => [1, $limit]
  153. ]));
  154. }
  155. public static function getRankedMinId($id = false, $end = null, $limit = 10)
  156. {
  157. if(!$end || !$id) {
  158. return [];
  159. }
  160. return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY.$id, '+inf', $end, [
  161. 'withscores' => true,
  162. 'limit' => [0, $limit]
  163. ]));
  164. }
  165. public static function rewriteMastodonTypes($notification)
  166. {
  167. if(!$notification || !isset($notification['type'])) {
  168. return $notification;
  169. }
  170. if($notification['type'] === 'comment') {
  171. $notification['type'] = 'mention';
  172. }
  173. if($notification['type'] === 'share') {
  174. $notification['type'] = 'reblog';
  175. }
  176. return $notification;
  177. }
  178. public static function set($id, $val)
  179. {
  180. if(self::count($id) > 400) {
  181. Redis::zpopmin(self::CACHE_KEY . $id);
  182. }
  183. return Redis::zadd(self::CACHE_KEY . $id, $val, $val);
  184. }
  185. public static function del($id, $val)
  186. {
  187. Cache::forget('service:notification:' . $val);
  188. return Redis::zrem(self::CACHE_KEY . $id, $val);
  189. }
  190. public static function add($id, $val)
  191. {
  192. return self::set($id, $val);
  193. }
  194. public static function rem($id, $val)
  195. {
  196. return self::del($id, $val);
  197. }
  198. public static function count($id)
  199. {
  200. return Redis::zcount(self::CACHE_KEY . $id, '-inf', '+inf');
  201. }
  202. public static function getNotification($id)
  203. {
  204. $notification = Cache::remember('service:notification:'.$id, self::ITEM_CACHE_TTL, function() use($id) {
  205. $n = Notification::with('item')->find($id);
  206. if(!$n) {
  207. return null;
  208. }
  209. $account = AccountService::get($n->actor_id, true);
  210. if(!$account) {
  211. return null;
  212. }
  213. $fractal = new Fractal\Manager();
  214. $fractal->setSerializer(new ArraySerializer());
  215. $resource = new Fractal\Resource\Item($n, new NotificationTransformer());
  216. return $fractal->createData($resource)->toArray();
  217. });
  218. if(!$notification) {
  219. return;
  220. }
  221. if(isset($notification['account'])) {
  222. $notification['account'] = AccountService::get($notification['account']['id'], true);
  223. }
  224. return $notification;
  225. }
  226. public static function setNotification(Notification $notification)
  227. {
  228. return Cache::remember('service:notification:'.$notification->id, self::ITEM_CACHE_TTL, function() use($notification) {
  229. $fractal = new Fractal\Manager();
  230. $fractal->setSerializer(new ArraySerializer());
  231. $resource = new Fractal\Resource\Item($notification, new NotificationTransformer());
  232. return $fractal->createData($resource)->toArray();
  233. });
  234. }
  235. public static function warmCache($id, $stop = 400, $force = false)
  236. {
  237. if(self::count($id) == 0 || $force == true) {
  238. $ids = Notification::where('profile_id', $id)
  239. ->where('id', '>', self::getEpochId())
  240. ->orderByDesc('id')
  241. ->limit($stop)
  242. ->pluck('id');
  243. foreach($ids as $key) {
  244. self::set($id, $key);
  245. }
  246. return 1;
  247. }
  248. return 0;
  249. }
  250. }