NotificationService.php 8.7 KB

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