1
0

PublicApiController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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. UserFilter
  14. };
  15. use Auth,Cache;
  16. use Carbon\Carbon;
  17. use League\Fractal;
  18. use App\Transformer\Api\{
  19. AccountTransformer,
  20. StatusTransformer,
  21. };
  22. use App\Jobs\StatusPipeline\NewStatusPipeline;
  23. use League\Fractal\Serializer\ArraySerializer;
  24. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  25. class PublicApiController extends Controller
  26. {
  27. protected $fractal;
  28. public function __construct()
  29. {
  30. $this->middleware('throttle:3000, 30');
  31. $this->fractal = new Fractal\Manager();
  32. $this->fractal->setSerializer(new ArraySerializer());
  33. }
  34. protected function getUserData()
  35. {
  36. if(false == Auth::check()) {
  37. return [];
  38. } else {
  39. $profile = Auth::user()->profile;
  40. $user = new Fractal\Resource\Item($profile, new AccountTransformer());
  41. return $this->fractal->createData($user)->toArray();
  42. }
  43. }
  44. protected function getLikes($status)
  45. {
  46. if(false == Auth::check()) {
  47. return [];
  48. } else {
  49. $profile = Auth::user()->profile;
  50. $likes = $status->likedBy()->orderBy('created_at','desc')->paginate(10);
  51. $collection = new Fractal\Resource\Collection($likes, new AccountTransformer());
  52. return $this->fractal->createData($collection)->toArray();
  53. }
  54. }
  55. protected function getShares($status)
  56. {
  57. if(false == Auth::check()) {
  58. return [];
  59. } else {
  60. $profile = Auth::user()->profile;
  61. $shares = $status->sharedBy()->orderBy('created_at','desc')->paginate(10);
  62. $collection = new Fractal\Resource\Collection($shares, new AccountTransformer());
  63. return $this->fractal->createData($collection)->toArray();
  64. }
  65. }
  66. public function status(Request $request, $username, int $postid)
  67. {
  68. $profile = Profile::whereUsername($username)->first();
  69. $status = Status::whereProfileId($profile->id)->find($postid);
  70. $this->scopeCheck($profile, $status);
  71. $item = new Fractal\Resource\Item($status, new StatusTransformer());
  72. $res = [
  73. 'status' => $this->fractal->createData($item)->toArray(),
  74. 'user' => $this->getUserData(),
  75. 'likes' => $this->getLikes($status),
  76. 'shares' => $this->getShares($status),
  77. 'reactions' => [
  78. 'liked' => $status->liked(),
  79. 'shared' => $status->shared(),
  80. 'bookmarked' => $status->bookmarked(),
  81. ],
  82. ];
  83. return response()->json($res, 200, [], JSON_PRETTY_PRINT);
  84. }
  85. public function statusComments(Request $request, $username, int $postId)
  86. {
  87. $this->validate($request, [
  88. 'min_id' => 'nullable|integer|min:1',
  89. 'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
  90. 'limit' => 'nullable|integer|min:5|max:50'
  91. ]);
  92. $limit = $request->limit ?? 10;
  93. $profile = Profile::whereUsername($username)->first();
  94. $status = Status::whereProfileId($profile->id)->find($postId);
  95. $this->scopeCheck($profile, $status);
  96. if($request->filled('min_id') || $request->filled('max_id')) {
  97. if($request->filled('min_id')) {
  98. $replies = $status->comments()
  99. ->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'created_at')
  100. ->where('id', '>=', $request->min_id)
  101. ->orderBy('id', 'desc')
  102. ->paginate($limit);
  103. }
  104. if($request->filled('max_id')) {
  105. $replies = $status->comments()
  106. ->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'created_at')
  107. ->where('id', '<=', $request->max_id)
  108. ->orderBy('id', 'desc')
  109. ->paginate($limit);
  110. }
  111. } else {
  112. $replies = $status->comments()
  113. ->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'created_at')
  114. ->orderBy('id', 'desc')
  115. ->paginate($limit);
  116. }
  117. $resource = new Fractal\Resource\Collection($replies, new StatusTransformer(), 'data');
  118. $resource->setPaginator(new IlluminatePaginatorAdapter($replies));
  119. $res = $this->fractal->createData($resource)->toArray();
  120. return response()->json($res, 200, [], JSON_PRETTY_PRINT);
  121. }
  122. public function statusLikes(Request $request, $username, $id)
  123. {
  124. $profile = Profile::whereUsername($username)->first();
  125. $status = Status::whereProfileId($profile->id)->find($id);
  126. $this->scopeCheck($profile, $status);
  127. $likes = $this->getLikes($status);
  128. return response()->json([
  129. 'data' => $likes
  130. ]);
  131. }
  132. public function statusShares(Request $request, $username, $id)
  133. {
  134. $profile = Profile::whereUsername($username)->first();
  135. $status = Status::whereProfileId($profile->id)->find($id);
  136. $this->scopeCheck($profile, $status);
  137. $shares = $this->getShares($status);
  138. return response()->json([
  139. 'data' => $shares
  140. ]);
  141. }
  142. protected function scopeCheck(Profile $profile, Status $status)
  143. {
  144. if($profile->is_private == true && Auth::check() == false) {
  145. abort(404);
  146. }
  147. switch ($status->scope) {
  148. case 'public':
  149. case 'unlisted':
  150. case 'private':
  151. $user = Auth::check() ? Auth::user() : false;
  152. if($user && $profile->is_private) {
  153. $follows = Follower::whereProfileId($user->profile->id)
  154. ->whereFollowingId($profile->id)
  155. ->exists();
  156. if($follows == false && $profile->id !== $user->profile->id) {
  157. abort(404);
  158. }
  159. }
  160. break;
  161. case 'direct':
  162. abort(404);
  163. break;
  164. case 'draft':
  165. abort(404);
  166. break;
  167. default:
  168. abort(404);
  169. break;
  170. }
  171. }
  172. public function publicTimelineApi(Request $request)
  173. {
  174. if(!Auth::check()) {
  175. return abort(403);
  176. }
  177. $this->validate($request,[
  178. 'page' => 'nullable|integer|max:40',
  179. 'min_id' => 'nullable|integer',
  180. 'max_id' => 'nullable|integer',
  181. 'limit' => 'nullable|integer|max:20'
  182. ]);
  183. $page = $request->input('page');
  184. $min = $request->input('min_id');
  185. $max = $request->input('max_id');
  186. $limit = $request->input('limit') ?? 10;
  187. // TODO: Use redis for timelines
  188. // $timeline = Timeline::build()->local();
  189. $pid = Auth::user()->profile->id;
  190. $private = Profile::whereIsPrivate(true)->where('id', '!=', $pid)->pluck('id');
  191. $filters = UserFilter::whereUserId($pid)
  192. ->whereFilterableType('App\Profile')
  193. ->whereIn('filter_type', ['mute', 'block'])
  194. ->pluck('filterable_id')->toArray();
  195. $filtered = array_merge($private->toArray(), $filters);
  196. if($min || $max) {
  197. $dir = $min ? '>' : '<';
  198. $id = $min ?? $max;
  199. $timeline = Status::whereHas('media')
  200. ->where('id', $dir, $id)
  201. ->whereNotIn('profile_id', $filtered)
  202. ->whereNull('in_reply_to_id')
  203. ->whereNull('reblog_of_id')
  204. ->whereVisibility('public')
  205. ->withCount(['comments', 'likes'])
  206. ->orderBy('created_at', 'desc')
  207. ->limit($limit)
  208. ->get();
  209. } else {
  210. $timeline = Status::whereHas('media')
  211. ->whereNotIn('profile_id', $filtered)
  212. ->whereNull('in_reply_to_id')
  213. ->whereNull('reblog_of_id')
  214. ->whereVisibility('public')
  215. ->withCount(['comments', 'likes'])
  216. ->orderBy('created_at', 'desc')
  217. ->simplePaginate($limit);
  218. }
  219. $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  220. $res = $this->fractal->createData($fractal)->toArray();
  221. return response()->json($res);
  222. }
  223. public function homeTimelineApi(Request $request)
  224. {
  225. if(!Auth::check()) {
  226. return abort(403);
  227. }
  228. $this->validate($request,[
  229. 'page' => 'nullable|integer|max:40',
  230. 'min_id' => 'nullable|integer',
  231. 'max_id' => 'nullable|integer',
  232. 'limit' => 'nullable|integer|max:20'
  233. ]);
  234. $page = $request->input('page');
  235. $min = $request->input('min_id');
  236. $max = $request->input('max_id');
  237. $limit = $request->input('limit') ?? 10;
  238. // TODO: Use redis for timelines
  239. // $timeline = Timeline::build()->local();
  240. $pid = Auth::user()->profile->id;
  241. $following = Follower::whereProfileId($pid)->pluck('following_id');
  242. $following->push($pid)->toArray();
  243. $private = Profile::whereIsPrivate(true)->where('id', '!=', $pid)->pluck('id');
  244. $filters = UserFilter::whereUserId($pid)
  245. ->whereFilterableType('App\Profile')
  246. ->whereIn('filter_type', ['mute', 'block'])
  247. ->pluck('filterable_id')->toArray();
  248. $filtered = array_merge($private->toArray(), $filters);
  249. if($min || $max) {
  250. $dir = $min ? '>' : '<';
  251. $id = $min ?? $max;
  252. $timeline = Status::whereHas('media')
  253. ->where('id', $dir, $id)
  254. ->whereIn('profile_id', $following)
  255. ->whereNotIn('profile_id', $filtered)
  256. ->whereNull('in_reply_to_id')
  257. ->whereNull('reblog_of_id')
  258. ->whereVisibility('public')
  259. ->withCount(['comments', 'likes'])
  260. ->orderBy('created_at', 'desc')
  261. ->limit($limit)
  262. ->get();
  263. } else {
  264. $timeline = Status::whereHas('media')
  265. ->whereIn('profile_id', $following)
  266. ->whereNotIn('profile_id', $filtered)
  267. ->whereNull('in_reply_to_id')
  268. ->whereNull('reblog_of_id')
  269. ->whereVisibility('public')
  270. ->withCount(['comments', 'likes'])
  271. ->orderBy('created_at', 'desc')
  272. ->simplePaginate($limit);
  273. }
  274. $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  275. $res = $this->fractal->createData($fractal)->toArray();
  276. return response()->json($res);
  277. }
  278. }