PublicApiController.php 11 KB

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