PublicApiController.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use App\{
  5. Hashtag,
  6. Follower,
  7. Like,
  8. Media,
  9. Notification,
  10. Profile,
  11. StatusHashtag,
  12. Status,
  13. StatusView,
  14. UserFilter
  15. };
  16. use Auth,Cache;
  17. use Carbon\Carbon;
  18. use League\Fractal;
  19. use App\Transformer\Api\{
  20. AccountTransformer,
  21. RelationshipTransformer,
  22. StatusTransformer,
  23. StatusStatelessTransformer
  24. };
  25. use App\Services\{
  26. AccountService,
  27. PublicTimelineService,
  28. UserFilterService
  29. };
  30. use App\Jobs\StatusPipeline\NewStatusPipeline;
  31. use League\Fractal\Serializer\ArraySerializer;
  32. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  33. class PublicApiController extends Controller
  34. {
  35. protected $fractal;
  36. public function __construct()
  37. {
  38. $this->fractal = new Fractal\Manager();
  39. $this->fractal->setSerializer(new ArraySerializer());
  40. }
  41. protected function getUserData($user)
  42. {
  43. if(!$user) {
  44. return [];
  45. } else {
  46. return AccountService::get($user->profile_id);
  47. }
  48. }
  49. protected function getLikes($status)
  50. {
  51. if(false == Auth::check()) {
  52. return [];
  53. } else {
  54. $profile = Auth::user()->profile;
  55. if($profile->status) {
  56. return [];
  57. }
  58. $likes = $status->likedBy()->orderBy('created_at','desc')->paginate(10);
  59. $collection = new Fractal\Resource\Collection($likes, new AccountTransformer());
  60. return $this->fractal->createData($collection)->toArray();
  61. }
  62. }
  63. protected function getShares($status)
  64. {
  65. if(false == Auth::check()) {
  66. return [];
  67. } else {
  68. $profile = Auth::user()->profile;
  69. if($profile->status) {
  70. return [];
  71. }
  72. $shares = $status->sharedBy()->orderBy('created_at','desc')->paginate(10);
  73. $collection = new Fractal\Resource\Collection($shares, new AccountTransformer());
  74. return $this->fractal->createData($collection)->toArray();
  75. }
  76. }
  77. public function status(Request $request, $username, int $postid)
  78. {
  79. $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
  80. $status = Status::whereProfileId($profile->id)->findOrFail($postid);
  81. $this->scopeCheck($profile, $status);
  82. if(!Auth::check()) {
  83. $res = Cache::remember('wapi:v1:status:stateless_byid:' . $status->id, now()->addMinutes(30), function() use($status) {
  84. $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
  85. $res = [
  86. 'status' => $this->fractal->createData($item)->toArray(),
  87. ];
  88. return $res;
  89. });
  90. return response()->json($res);
  91. }
  92. $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
  93. $res = [
  94. 'status' => $this->fractal->createData($item)->toArray(),
  95. ];
  96. return response()->json($res);
  97. }
  98. public function statusState(Request $request, $username, int $postid)
  99. {
  100. $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
  101. $status = Status::whereProfileId($profile->id)->findOrFail($postid);
  102. $this->scopeCheck($profile, $status);
  103. if(!Auth::check()) {
  104. $res = [
  105. 'user' => [],
  106. 'likes' => [],
  107. 'shares' => [],
  108. 'reactions' => [
  109. 'liked' => false,
  110. 'shared' => false,
  111. 'bookmarked' => false,
  112. ],
  113. ];
  114. return response()->json($res);
  115. }
  116. $res = [
  117. 'user' => $this->getUserData($request->user()),
  118. 'likes' => [],
  119. 'shares' => [],
  120. 'reactions' => [
  121. 'liked' => (bool) $status->liked(),
  122. 'shared' => (bool) $status->shared(),
  123. 'bookmarked' => (bool) $status->bookmarked(),
  124. ],
  125. ];
  126. return response()->json($res);
  127. }
  128. public function statusComments(Request $request, $username, int $postId)
  129. {
  130. $this->validate($request, [
  131. 'min_id' => 'nullable|integer|min:1',
  132. 'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
  133. 'limit' => 'nullable|integer|min:5|max:50'
  134. ]);
  135. $limit = $request->limit ?? 10;
  136. $profile = Profile::whereNull('status')->findOrFail($username);
  137. $status = Status::whereProfileId($profile->id)->whereCommentsDisabled(false)->findOrFail($postId);
  138. $this->scopeCheck($profile, $status);
  139. if(Auth::check()) {
  140. $p = Auth::user()->profile;
  141. $filtered = UserFilter::whereUserId($p->id)
  142. ->whereFilterableType('App\Profile')
  143. ->whereIn('filter_type', ['mute', 'block'])
  144. ->pluck('filterable_id')->toArray();
  145. $scope = $p->id == $status->profile_id ? ['public', 'private', 'unlisted'] : ['public','unlisted'];
  146. } else {
  147. $filtered = [];
  148. $scope = ['public', 'unlisted'];
  149. }
  150. if($request->filled('min_id') || $request->filled('max_id')) {
  151. if($request->filled('min_id')) {
  152. $replies = $status->comments()
  153. ->whereNull('reblog_of_id')
  154. ->whereIn('scope', $scope)
  155. ->whereNotIn('profile_id', $filtered)
  156. ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
  157. ->where('id', '>=', $request->min_id)
  158. ->orderBy('id', 'desc')
  159. ->paginate($limit);
  160. }
  161. if($request->filled('max_id')) {
  162. $replies = $status->comments()
  163. ->whereNull('reblog_of_id')
  164. ->whereIn('scope', $scope)
  165. ->whereNotIn('profile_id', $filtered)
  166. ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
  167. ->where('id', '<=', $request->max_id)
  168. ->orderBy('id', 'desc')
  169. ->paginate($limit);
  170. }
  171. } else {
  172. $replies = $status->comments()
  173. ->whereNull('reblog_of_id')
  174. ->whereIn('scope', $scope)
  175. ->whereNotIn('profile_id', $filtered)
  176. ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
  177. ->orderBy('id', 'desc')
  178. ->paginate($limit);
  179. }
  180. $resource = new Fractal\Resource\Collection($replies, new StatusTransformer(), 'data');
  181. $resource->setPaginator(new IlluminatePaginatorAdapter($replies));
  182. $res = $this->fractal->createData($resource)->toArray();
  183. return response()->json($res, 200, [], JSON_PRETTY_PRINT);
  184. }
  185. public function statusLikes(Request $request, $username, $id)
  186. {
  187. abort_if(!$request->user(), 404);
  188. $status = Status::findOrFail($id);
  189. $this->scopeCheck($status->profile, $status);
  190. $page = $request->input('page');
  191. if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
  192. return response()->json([
  193. 'data' => []
  194. ]);
  195. }
  196. $likes = $this->getLikes($status);
  197. return response()->json([
  198. 'data' => $likes
  199. ]);
  200. }
  201. public function statusShares(Request $request, $username, $id)
  202. {
  203. abort_if(!$request->user(), 404);
  204. $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
  205. $status = Status::whereProfileId($profile->id)->findOrFail($id);
  206. $this->scopeCheck($profile, $status);
  207. $page = $request->input('page');
  208. if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
  209. return response()->json([
  210. 'data' => []
  211. ]);
  212. }
  213. $shares = $this->getShares($status);
  214. return response()->json([
  215. 'data' => $shares
  216. ]);
  217. }
  218. protected function scopeCheck(Profile $profile, Status $status)
  219. {
  220. if($profile->is_private == true && Auth::check() == false) {
  221. abort(404);
  222. }
  223. switch ($status->scope) {
  224. case 'public':
  225. case 'unlisted':
  226. break;
  227. case 'private':
  228. $user = Auth::check() ? Auth::user() : false;
  229. if(!$user) {
  230. abort(403);
  231. } else {
  232. $follows = $profile->followedBy($user->profile);
  233. if($follows == false && $profile->id !== $user->profile->id && $user->is_admin == false) {
  234. abort(404);
  235. }
  236. }
  237. break;
  238. case 'direct':
  239. abort(404);
  240. break;
  241. case 'draft':
  242. abort(404);
  243. break;
  244. default:
  245. abort(404);
  246. break;
  247. }
  248. }
  249. public function publicTimelineApi(Request $request)
  250. {
  251. $this->validate($request,[
  252. 'page' => 'nullable|integer|max:40',
  253. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  254. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  255. 'limit' => 'nullable|integer|max:30'
  256. ]);
  257. if(config('instance.timeline.local.is_public') == false && !Auth::check()) {
  258. abort(403, 'Authentication required.');
  259. }
  260. $page = $request->input('page');
  261. $min = $request->input('min_id');
  262. $max = $request->input('max_id');
  263. $limit = $request->input('limit') ?? 3;
  264. $user = $request->user();
  265. $key = 'user:last_active_at:id:'.$user->id;
  266. $ttl = now()->addMinutes(5);
  267. Cache::remember($key, $ttl, function() use($user) {
  268. $user->last_active_at = now();
  269. $user->save();
  270. return;
  271. });
  272. $filtered = UserFilter::whereUserId($user->profile_id)
  273. ->whereFilterableType('App\Profile')
  274. ->whereIn('filter_type', ['mute', 'block'])
  275. ->pluck('filterable_id')->toArray();
  276. if($min || $max) {
  277. $dir = $min ? '>' : '<';
  278. $id = $min ?? $max;
  279. $timeline = Status::select(
  280. 'id',
  281. 'uri',
  282. 'caption',
  283. 'rendered',
  284. 'profile_id',
  285. 'type',
  286. 'in_reply_to_id',
  287. 'reblog_of_id',
  288. 'is_nsfw',
  289. 'scope',
  290. 'local',
  291. 'reply_count',
  292. 'comments_disabled',
  293. 'place_id',
  294. 'likes_count',
  295. 'reblogs_count',
  296. 'created_at',
  297. 'updated_at'
  298. )->where('id', $dir, $id)
  299. ->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  300. ->whereNotIn('profile_id', $filtered)
  301. ->whereLocal(true)
  302. ->whereScope('public')
  303. ->where('created_at', '>', now()->subMonths(6))
  304. ->orderBy('created_at', 'desc')
  305. ->limit($limit)
  306. ->get();
  307. } else {
  308. $timeline = Status::select(
  309. 'id',
  310. 'uri',
  311. 'caption',
  312. 'rendered',
  313. 'profile_id',
  314. 'type',
  315. 'in_reply_to_id',
  316. 'reblog_of_id',
  317. 'is_nsfw',
  318. 'scope',
  319. 'local',
  320. 'reply_count',
  321. 'comments_disabled',
  322. 'created_at',
  323. 'place_id',
  324. 'likes_count',
  325. 'reblogs_count',
  326. 'updated_at'
  327. )->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  328. ->whereNotIn('profile_id', $filtered)
  329. ->with('profile', 'hashtags', 'mentions')
  330. ->whereLocal(true)
  331. ->whereScope('public')
  332. ->where('created_at', '>', now()->subMonths(6))
  333. ->orderBy('created_at', 'desc')
  334. ->simplePaginate($limit);
  335. }
  336. $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  337. $res = $this->fractal->createData($fractal)->toArray();
  338. return response()->json($res);
  339. }
  340. public function homeTimelineApi(Request $request)
  341. {
  342. if(!Auth::check()) {
  343. return abort(403);
  344. }
  345. $this->validate($request,[
  346. 'page' => 'nullable|integer|max:40',
  347. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  348. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  349. 'limit' => 'nullable|integer|max:40',
  350. 'recent_feed' => 'nullable',
  351. 'recent_min' => 'nullable|integer'
  352. ]);
  353. $recentFeed = $request->input('recent_feed') == 'true';
  354. $recentFeedMin = $request->input('recent_min');
  355. $page = $request->input('page');
  356. $min = $request->input('min_id');
  357. $max = $request->input('max_id');
  358. $limit = $request->input('limit') ?? 3;
  359. $user = $request->user();
  360. $key = 'user:last_active_at:id:'.$user->id;
  361. $ttl = now()->addMinutes(5);
  362. Cache::remember($key, $ttl, function() use($user) {
  363. $user->last_active_at = now();
  364. $user->save();
  365. return;
  366. });
  367. $pid = Auth::user()->profile_id;
  368. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  369. $following = Follower::whereProfileId($pid)->pluck('following_id');
  370. return $following->push($pid)->toArray();
  371. });
  372. if($recentFeed == true) {
  373. $key = 'profile:home-timeline-cursor:'.$user->id;
  374. $ttl = now()->addMinutes(30);
  375. $min = Cache::remember($key, $ttl, function() use($pid) {
  376. $res = StatusView::whereProfileId($pid)->orderByDesc('status_id')->first();
  377. return $res ? $res->status_id : null;
  378. });
  379. }
  380. $filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
  381. if($min || $max) {
  382. $dir = $min ? '>' : '<';
  383. $id = $min ?? $max;
  384. $timeline = Status::select(
  385. 'id',
  386. 'uri',
  387. 'caption',
  388. 'rendered',
  389. 'profile_id',
  390. 'type',
  391. 'in_reply_to_id',
  392. 'reblog_of_id',
  393. 'is_nsfw',
  394. 'scope',
  395. 'local',
  396. 'reply_count',
  397. 'comments_disabled',
  398. 'place_id',
  399. 'likes_count',
  400. 'reblogs_count',
  401. 'created_at',
  402. 'updated_at'
  403. )->whereIn('type', ['text','photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  404. ->with('profile', 'hashtags', 'mentions')
  405. ->where('id', $dir, $id)
  406. ->whereIn('profile_id', $following)
  407. ->whereNotIn('profile_id', $filtered)
  408. ->whereIn('visibility',['public', 'unlisted', 'private'])
  409. ->orderBy('created_at', 'desc')
  410. ->limit($limit)
  411. ->get();
  412. } else {
  413. $timeline = Status::select(
  414. 'id',
  415. 'uri',
  416. 'caption',
  417. 'rendered',
  418. 'profile_id',
  419. 'type',
  420. 'in_reply_to_id',
  421. 'reblog_of_id',
  422. 'is_nsfw',
  423. 'scope',
  424. 'local',
  425. 'reply_count',
  426. 'comments_disabled',
  427. 'place_id',
  428. 'likes_count',
  429. 'reblogs_count',
  430. 'created_at',
  431. 'updated_at'
  432. )->whereIn('type', ['text','photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  433. ->with('profile', 'hashtags', 'mentions')
  434. ->whereIn('profile_id', $following)
  435. ->whereNotIn('profile_id', $filtered)
  436. ->whereIn('visibility',['public', 'unlisted', 'private'])
  437. ->orderBy('created_at', 'desc')
  438. ->simplePaginate($limit);
  439. }
  440. $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  441. $res = $this->fractal->createData($fractal)->toArray();
  442. return response()->json($res);
  443. }
  444. public function networkTimelineApi(Request $request)
  445. {
  446. abort_if(!Auth::check(), 403);
  447. abort_if(config('federation.network_timeline') == false, 404);
  448. $this->validate($request,[
  449. 'page' => 'nullable|integer|max:40',
  450. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  451. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  452. 'limit' => 'nullable|integer|max:30'
  453. ]);
  454. $page = $request->input('page');
  455. $min = $request->input('min_id');
  456. $max = $request->input('max_id');
  457. $limit = $request->input('limit') ?? 3;
  458. $user = $request->user();
  459. $key = 'user:last_active_at:id:'.$user->id;
  460. $ttl = now()->addMinutes(5);
  461. Cache::remember($key, $ttl, function() use($user) {
  462. $user->last_active_at = now();
  463. $user->save();
  464. return;
  465. });
  466. if($min || $max) {
  467. $dir = $min ? '>' : '<';
  468. $id = $min ?? $max;
  469. $timeline = Status::select(
  470. 'id',
  471. 'uri',
  472. 'caption',
  473. 'rendered',
  474. 'profile_id',
  475. 'type',
  476. 'in_reply_to_id',
  477. 'reblog_of_id',
  478. 'is_nsfw',
  479. 'scope',
  480. 'local',
  481. 'reply_count',
  482. 'comments_disabled',
  483. 'place_id',
  484. 'likes_count',
  485. 'reblogs_count',
  486. 'created_at',
  487. 'updated_at'
  488. )->where('id', $dir, $id)
  489. ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  490. ->whereNotNull('uri')
  491. ->whereScope('public')
  492. ->where('created_at', '>', now()->subMonths(3))
  493. ->orderBy('created_at', 'desc')
  494. ->limit($limit)
  495. ->get();
  496. } else {
  497. $timeline = Status::select(
  498. 'id',
  499. 'uri',
  500. 'caption',
  501. 'rendered',
  502. 'profile_id',
  503. 'type',
  504. 'in_reply_to_id',
  505. 'reblog_of_id',
  506. 'is_nsfw',
  507. 'scope',
  508. 'local',
  509. 'reply_count',
  510. 'comments_disabled',
  511. 'created_at',
  512. 'place_id',
  513. 'likes_count',
  514. 'reblogs_count',
  515. 'updated_at'
  516. )->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  517. ->with('profile', 'hashtags', 'mentions')
  518. ->whereNotNull('uri')
  519. ->whereScope('public')
  520. ->where('created_at', '>', now()->subMonths(3))
  521. ->orderBy('created_at', 'desc')
  522. ->simplePaginate($limit);
  523. }
  524. $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  525. $res = $this->fractal->createData($fractal)->toArray();
  526. return response()->json($res);
  527. }
  528. public function relationships(Request $request)
  529. {
  530. if(!Auth::check()) {
  531. return response()->json([]);
  532. }
  533. $this->validate($request, [
  534. 'id' => 'required|array|min:1|max:20',
  535. 'id.*' => 'required|integer'
  536. ]);
  537. $ids = collect($request->input('id'));
  538. $filtered = $ids->filter(function($v) {
  539. return $v != Auth::user()->profile->id;
  540. });
  541. $relations = Profile::whereNull('status')->findOrFail($filtered->all());
  542. $fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer());
  543. $res = $this->fractal->createData($fractal)->toArray();
  544. return response()->json($res);
  545. }
  546. public function account(Request $request, $id)
  547. {
  548. $res = AccountService::get($id);
  549. return response()->json($res);
  550. }
  551. public function accountFollowers(Request $request, $id)
  552. {
  553. abort_unless(Auth::check(), 403);
  554. $profile = Profile::with('user')->whereNull('status')->whereNull('domain')->findOrFail($id);
  555. if(Auth::id() != $profile->user_id && $profile->is_private || !$profile->user->settings->show_profile_followers) {
  556. return response()->json([]);
  557. }
  558. $followers = $profile->followers()->orderByDesc('followers.created_at')->paginate(10);
  559. $resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
  560. $res = $this->fractal->createData($resource)->toArray();
  561. return response()->json($res);
  562. }
  563. public function accountFollowing(Request $request, $id)
  564. {
  565. abort_unless(Auth::check(), 403);
  566. $profile = Profile::with('user')
  567. ->whereNull('status')
  568. ->whereNull('domain')
  569. ->findOrFail($id);
  570. // filter by username
  571. $search = $request->input('fbu');
  572. $owner = Auth::id() == $profile->user_id;
  573. $filter = ($owner == true) && ($search != null);
  574. abort_if($owner == false && $profile->is_private == true && !$profile->followedBy(Auth::user()->profile), 404);
  575. abort_if($profile->user->settings->show_profile_following == false && $owner == false, 404);
  576. if($search) {
  577. abort_if(!$owner, 404);
  578. $following = $profile->following()
  579. ->where('profiles.username', 'like', '%'.$search.'%')
  580. ->orderByDesc('followers.created_at')
  581. ->paginate(10);
  582. } else {
  583. $following = $profile->following()
  584. ->orderByDesc('followers.created_at')
  585. ->paginate(10);
  586. }
  587. $resource = new Fractal\Resource\Collection($following, new AccountTransformer());
  588. $res = $this->fractal->createData($resource)->toArray();
  589. return response()->json($res);
  590. }
  591. public function accountStatuses(Request $request, $id)
  592. {
  593. $this->validate($request, [
  594. 'only_media' => 'nullable',
  595. 'pinned' => 'nullable',
  596. 'exclude_replies' => 'nullable',
  597. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  598. 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  599. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  600. 'limit' => 'nullable|integer|min:1|max:24'
  601. ]);
  602. $profile = Profile::whereNull('status')->findOrFail($id);
  603. $limit = $request->limit ?? 9;
  604. $max_id = $request->max_id;
  605. $min_id = $request->min_id;
  606. $scope = $request->only_media == true ?
  607. ['photo', 'photo:album', 'video', 'video:album'] :
  608. ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
  609. if($profile->is_private) {
  610. if(!Auth::check()) {
  611. return response()->json([]);
  612. }
  613. $pid = Auth::user()->profile->id;
  614. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  615. $following = Follower::whereProfileId($pid)->pluck('following_id');
  616. return $following->push($pid)->toArray();
  617. });
  618. $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : [];
  619. } else {
  620. if(Auth::check()) {
  621. $pid = Auth::user()->profile->id;
  622. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  623. $following = Follower::whereProfileId($pid)->pluck('following_id');
  624. return $following->push($pid)->toArray();
  625. });
  626. $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
  627. } else {
  628. $visibility = ['public', 'unlisted'];
  629. }
  630. }
  631. $tag = in_array('private', $visibility) ? 'private' : 'public';
  632. if($min_id == 1 && $limit == 9 && $tag == 'public') {
  633. $limit = 9;
  634. $scope = ['photo', 'photo:album', 'video', 'video:album'];
  635. $key = '_api:statuses:recent_9:'.$profile->id;
  636. $res = Cache::remember($key, now()->addHours(24), function() use($profile, $scope, $visibility, $limit) {
  637. $dir = '>';
  638. $id = 1;
  639. $timeline = Status::select(
  640. 'id',
  641. 'uri',
  642. 'caption',
  643. 'rendered',
  644. 'profile_id',
  645. 'type',
  646. 'in_reply_to_id',
  647. 'reblog_of_id',
  648. 'is_nsfw',
  649. 'likes_count',
  650. 'reblogs_count',
  651. 'scope',
  652. 'visibility',
  653. 'local',
  654. 'place_id',
  655. 'comments_disabled',
  656. 'cw_summary',
  657. 'created_at',
  658. 'updated_at'
  659. )->whereProfileId($profile->id)
  660. ->whereIn('type', $scope)
  661. ->where('id', $dir, $id)
  662. ->whereIn('visibility', $visibility)
  663. ->limit($limit)
  664. ->orderByDesc('id')
  665. ->get();
  666. $resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer());
  667. $res = $this->fractal->createData($resource)->toArray();
  668. return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
  669. });
  670. return $res;
  671. }
  672. $dir = $min_id ? '>' : '<';
  673. $id = $min_id ?? $max_id;
  674. $timeline = Status::select(
  675. 'id',
  676. 'uri',
  677. 'caption',
  678. 'rendered',
  679. 'profile_id',
  680. 'type',
  681. 'in_reply_to_id',
  682. 'reblog_of_id',
  683. 'is_nsfw',
  684. 'likes_count',
  685. 'reblogs_count',
  686. 'scope',
  687. 'visibility',
  688. 'local',
  689. 'place_id',
  690. 'comments_disabled',
  691. 'cw_summary',
  692. 'created_at',
  693. 'updated_at'
  694. )->whereProfileId($profile->id)
  695. ->whereIn('type', $scope)
  696. ->where('id', $dir, $id)
  697. ->whereIn('visibility', $visibility)
  698. ->limit($limit)
  699. ->orderByDesc('id')
  700. ->get();
  701. $resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer());
  702. $res = $this->fractal->createData($resource)->toArray();
  703. return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
  704. }
  705. }