ApiV1Controller.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. <?php
  2. namespace App\Http\Controllers\Api;
  3. use Illuminate\Http\Request;
  4. use App\Http\Controllers\Controller;
  5. use Illuminate\Support\Str;
  6. use App\Util\ActivityPub\Helpers;
  7. use App\Jobs\LikePipeline\LikePipeline;
  8. use App\Jobs\StatusPipeline\StatusDelete;
  9. use App\Jobs\FollowPipeline\FollowPipeline;
  10. use Laravel\Passport\Passport;
  11. use Auth, Cache, DB;
  12. use App\{
  13. Follower,
  14. FollowRequest,
  15. Like,
  16. Media,
  17. Notification,
  18. Profile,
  19. Status,
  20. UserFilter,
  21. };
  22. use League\Fractal;
  23. use App\Transformer\Api\{
  24. AccountTransformer,
  25. RelationshipTransformer,
  26. StatusTransformer,
  27. };
  28. use App\Http\Controllers\FollowerController;
  29. use League\Fractal\Serializer\ArraySerializer;
  30. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  31. use App\Services\NotificationService;
  32. class ApiV1Controller extends Controller
  33. {
  34. protected $fractal;
  35. public function __construct()
  36. {
  37. $this->fractal = new Fractal\Manager();
  38. $this->fractal->setSerializer(new ArraySerializer());
  39. }
  40. public function apps(Request $request)
  41. {
  42. abort_if(!config('pixelfed.oauth_enabled'), 404);
  43. $this->validate($request, [
  44. 'client_name' => 'required',
  45. 'redirect_uris' => 'required',
  46. 'scopes' => 'nullable',
  47. 'website' => 'nullable'
  48. ]);
  49. $client = Passport::client()->forceFill([
  50. 'user_id' => null,
  51. 'name' => e($request->client_name),
  52. 'secret' => Str::random(40),
  53. 'redirect' => $request->redirect_uris,
  54. 'personal_access_client' => false,
  55. 'password_client' => false,
  56. 'revoked' => false,
  57. ]);
  58. $client->save();
  59. $res = [
  60. 'id' => $client->id,
  61. 'name' => $client->name,
  62. 'website' => null,
  63. 'redirect_uri' => $client->redirect,
  64. 'client_id' => $client->id,
  65. 'client_secret' => $client->secret,
  66. 'vapid_key' => null
  67. ];
  68. return $res;
  69. }
  70. /**
  71. * GET /api/v1/accounts/{id}
  72. *
  73. * @param integer $id
  74. *
  75. * @return \App\Transformer\Api\AccountTransformer
  76. */
  77. public function accountById(Request $request, $id)
  78. {
  79. $profile = Profile::whereNull('status')->findOrFail($id);
  80. $resource = new Fractal\Resource\Item($profile, new AccountTransformer());
  81. $res = $this->fractal->createData($resource)->toArray();
  82. return response()->json($res);
  83. }
  84. /**
  85. * PATCH /api/v1/accounts/update_credentials
  86. *
  87. * @return \App\Transformer\Api\AccountTransformer
  88. */
  89. public function accountUpdateCredentials(Request $request)
  90. {
  91. abort_if(!$request->user(), 403);
  92. $this->validate($request, [
  93. 'display_name' => 'nullable|string',
  94. 'note' => 'nullable|string',
  95. 'locked' => 'nullable|boolean',
  96. // 'source.privacy' => 'nullable|in:unlisted,public,private',
  97. // 'source.sensitive' => 'nullable|boolean'
  98. ]);
  99. $user = $request->user();
  100. $profile = $user->profile;
  101. $displayName = $request->input('display_name');
  102. $note = $request->input('note');
  103. $locked = $request->input('locked');
  104. // $privacy = $request->input('source.privacy');
  105. // $sensitive = $request->input('source.sensitive');
  106. $changes = false;
  107. if($displayName !== $user->name) {
  108. $user->name = $displayName;
  109. $profile->name = $displayName;
  110. $changes = true;
  111. }
  112. if($note !== $profile->bio) {
  113. $profile->bio = e($note);
  114. $changes = true;
  115. }
  116. if(!is_null($locked)) {
  117. $profile->is_private = $locked;
  118. $changes = true;
  119. }
  120. if($changes) {
  121. $user->save();
  122. $profile->save();
  123. }
  124. $resource = new Fractal\Resource\Item($profile, new AccountTransformer());
  125. $res = $this->fractal->createData($resource)->toArray();
  126. return response()->json($res);
  127. }
  128. /**
  129. * GET /api/v1/accounts/{id}/followers
  130. *
  131. * @param integer $id
  132. *
  133. * @return \App\Transformer\Api\AccountTransformer
  134. */
  135. public function accountFollowersById(Request $request, $id)
  136. {
  137. abort_if(!$request->user(), 403);
  138. $profile = Profile::whereNull('status')->findOrFail($id);
  139. $settings = $profile->user->settings;
  140. if($settings->show_profile_followers == true) {
  141. $limit = $request->input('limit') ?? 40;
  142. $followers = $profile->followers()->paginate($limit);
  143. $resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
  144. $res = $this->fractal->createData($resource)->toArray();
  145. } else {
  146. $res = [];
  147. }
  148. return response()->json($res);
  149. }
  150. /**
  151. * GET /api/v1/accounts/{id}/following
  152. *
  153. * @param integer $id
  154. *
  155. * @return \App\Transformer\Api\AccountTransformer
  156. */
  157. public function accountFollowingById(Request $request, $id)
  158. {
  159. abort_if(!$request->user(), 403);
  160. $profile = Profile::whereNull('status')->findOrFail($id);
  161. $settings = $profile->user->settings;
  162. if($settings->show_profile_following == true) {
  163. $limit = $request->input('limit') ?? 40;
  164. $following = $profile->following()->paginate($limit);
  165. $resource = new Fractal\Resource\Collection($following, new AccountTransformer());
  166. $res = $this->fractal->createData($resource)->toArray();
  167. } else {
  168. $res = [];
  169. }
  170. return response()->json($res);
  171. }
  172. /**
  173. * GET /api/v1/accounts/{id}/statuses
  174. *
  175. * @param integer $id
  176. *
  177. * @return \App\Transformer\Api\StatusTransformer
  178. */
  179. public function accountStatusesById(Request $request, $id)
  180. {
  181. abort_if(!$request->user(), 403);
  182. $this->validate($request, [
  183. 'only_media' => 'nullable',
  184. 'pinned' => 'nullable',
  185. 'exclude_replies' => 'nullable',
  186. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  187. 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  188. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  189. 'limit' => 'nullable|integer|min:1|max:40'
  190. ]);
  191. $profile = Profile::whereNull('status')->findOrFail($id);
  192. $limit = $request->limit ?? 20;
  193. $max_id = $request->max_id;
  194. $min_id = $request->min_id;
  195. $pid = $request->user()->profile_id;
  196. $scope = $request->only_media == true ?
  197. ['photo', 'photo:album', 'video', 'video:album'] :
  198. ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
  199. if($pid == $profile->id) {
  200. $visibility = ['public', 'unlisted', 'private'];
  201. } else if($profile->is_private) {
  202. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  203. $following = Follower::whereProfileId($pid)->pluck('following_id');
  204. return $following->push($pid)->toArray();
  205. });
  206. $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : [];
  207. } else {
  208. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  209. $following = Follower::whereProfileId($pid)->pluck('following_id');
  210. return $following->push($pid)->toArray();
  211. });
  212. $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
  213. }
  214. if($min_id || $max_id) {
  215. $dir = $min_id ? '>' : '<';
  216. $id = $min_id ?? $max_id;
  217. $timeline = Status::select(
  218. 'id',
  219. 'uri',
  220. 'caption',
  221. 'rendered',
  222. 'profile_id',
  223. 'type',
  224. 'in_reply_to_id',
  225. 'reblog_of_id',
  226. 'is_nsfw',
  227. 'scope',
  228. 'local',
  229. 'place_id',
  230. 'created_at',
  231. 'updated_at'
  232. )->whereProfileId($profile->id)
  233. ->whereIn('type', $scope)
  234. ->where('id', $dir, $id)
  235. ->whereIn('visibility', $visibility)
  236. ->latest()
  237. ->limit($limit)
  238. ->get();
  239. } else {
  240. $timeline = Status::select(
  241. 'id',
  242. 'uri',
  243. 'caption',
  244. 'rendered',
  245. 'profile_id',
  246. 'type',
  247. 'in_reply_to_id',
  248. 'reblog_of_id',
  249. 'is_nsfw',
  250. 'scope',
  251. 'local',
  252. 'place_id',
  253. 'created_at',
  254. 'updated_at'
  255. )->whereProfileId($profile->id)
  256. ->whereIn('type', $scope)
  257. ->whereIn('visibility', $visibility)
  258. ->latest()
  259. ->limit($limit)
  260. ->get();
  261. }
  262. $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  263. $res = $this->fractal->createData($resource)->toArray();
  264. return response()->json($res);
  265. }
  266. /**
  267. * POST /api/v1/accounts/{id}/follow
  268. *
  269. * @param integer $id
  270. *
  271. * @return \App\Transformer\Api\RelationshipTransformer
  272. */
  273. public function accountFollowById(Request $request, $id)
  274. {
  275. abort_if(!$request->user(), 403);
  276. $user = $request->user();
  277. $target = Profile::where('id', '!=', $user->id)
  278. ->whereNull('status')
  279. ->findOrFail($item);
  280. $private = (bool) $target->is_private;
  281. $remote = (bool) $target->domain;
  282. $blocked = UserFilter::whereUserId($target->id)
  283. ->whereFilterType('block')
  284. ->whereFilterableId($user->id)
  285. ->whereFilterableType('App\Profile')
  286. ->exists();
  287. if($blocked == true) {
  288. abort(400, 'You cannot follow this user.');
  289. }
  290. $isFollowing = Follower::whereProfileId($user->id)
  291. ->whereFollowingId($target->id)
  292. ->exists();
  293. // Following already, return empty relationship
  294. if($isFollowing == true) {
  295. $resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
  296. $res = $this->fractal->createData($resource)->toArray();
  297. return response()->json($res);
  298. }
  299. // Rate limits, max 7500 followers per account
  300. if($user->following()->count() >= Follower::MAX_FOLLOWING) {
  301. abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts');
  302. }
  303. // Rate limits, follow 30 accounts per hour max
  304. if($user->following()->where('followers.created_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
  305. abort(400, 'You can only follow ' . Follower::FOLLOW_PER_HOUR . ' users per hour');
  306. }
  307. if($private == true) {
  308. $follow = FollowRequest::firstOrCreate([
  309. 'follower_id' => $user->id,
  310. 'following_id' => $target->id
  311. ]);
  312. if($remote == true && config('federation.activitypub.remoteFollow') == true) {
  313. (new FollowerController())->sendFollow($user, $target);
  314. }
  315. } else {
  316. $follower = new Follower();
  317. $follower->profile_id = $user->id;
  318. $follower->following_id = $target->id;
  319. $follower->save();
  320. if($remote == true && config('federation.activitypub.remoteFollow') == true) {
  321. (new FollowerController())->sendFollow($user, $target);
  322. }
  323. FollowPipeline::dispatch($follower);
  324. }
  325. Cache::forget('profile:following:'.$target->id);
  326. Cache::forget('profile:followers:'.$target->id);
  327. Cache::forget('profile:following:'.$user->id);
  328. Cache::forget('profile:followers:'.$user->id);
  329. Cache::forget('api:local:exp:rec:'.$user->id);
  330. Cache::forget('user:account:id:'.$target->user_id);
  331. Cache::forget('user:account:id:'.$user->user_id);
  332. $resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
  333. $res = $this->fractal->createData($resource)->toArray();
  334. return response()->json($res);
  335. }
  336. /**
  337. * POST /api/v1/accounts/{id}/unfollow
  338. *
  339. * @param integer $id
  340. *
  341. * @return \App\Transformer\Api\RelationshipTransformer
  342. */
  343. public function accountUnfollowById(Request $request, $id)
  344. {
  345. abort_if(!$request->user(), 403);
  346. $user = $request->user();
  347. $target = Profile::where('id', '!=', $user->id)
  348. ->whereNull('status')
  349. ->findOrFail($item);
  350. $private = (bool) $target->is_private;
  351. $remote = (bool) $target->domain;
  352. $isFollowing = Follower::whereProfileId($user->id)
  353. ->whereFollowingId($target->id)
  354. ->exists();
  355. if($isFollowing == false) {
  356. $resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
  357. $res = $this->fractal->createData($resource)->toArray();
  358. return response()->json($res);
  359. }
  360. // Rate limits, follow 30 accounts per hour max
  361. if($user->following()->where('followers.updated_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
  362. abort(400, 'You can only follow or unfollow ' . Follower::FOLLOW_PER_HOUR . ' users per hour');
  363. }
  364. FollowRequest::whereFollowerId($user->id)
  365. ->whereFollowingId($target->id)
  366. ->delete();
  367. Follower::whereProfileId($user->id)
  368. ->whereFollowingId($target->id)
  369. ->delete();
  370. if($remote == true && config('federation.activitypub.remoteFollow') == true) {
  371. (new FollowerController())->sendUndoFollow($user, $target);
  372. }
  373. Cache::forget('profile:following:'.$target->id);
  374. Cache::forget('profile:followers:'.$target->id);
  375. Cache::forget('profile:following:'.$user->id);
  376. Cache::forget('profile:followers:'.$user->id);
  377. Cache::forget('api:local:exp:rec:'.$user->id);
  378. Cache::forget('user:account:id:'.$target->user_id);
  379. Cache::forget('user:account:id:'.$user->user_id);
  380. $resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
  381. $res = $this->fractal->createData($resource)->toArray();
  382. return response()->json($res);
  383. }
  384. /**
  385. * GET /api/v1/accounts/relationships
  386. *
  387. * @param array|integer $id
  388. *
  389. * @return \App\Transformer\Api\RelationshipTransformer
  390. */
  391. public function accountRelationshipsById(Request $request)
  392. {
  393. abort_if(!$request->user(), 403);
  394. $this->validate($request, [
  395. 'id' => 'required|array|min:1|max:20',
  396. 'id.*' => 'required|integer|min:1|max:' . PHP_INT_MAX
  397. ]);
  398. $pid = $request->user()->profile_id ?? $request->user()->profile->id;
  399. $ids = collect($request->input('id'));
  400. $filtered = $ids->filter(function($v) use($pid) {
  401. return $v != $pid;
  402. });
  403. $relations = Profile::whereNull('status')->findOrFail($filtered->values());
  404. $fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer());
  405. $res = $this->fractal->createData($fractal)->toArray();
  406. return response()->json($res);
  407. }
  408. /**
  409. * GET /api/v1/accounts/search
  410. *
  411. *
  412. *
  413. * @return \App\Transformer\Api\AccountTransformer
  414. */
  415. public function accountSearch(Request $request)
  416. {
  417. abort_if(!$request->user(), 403);
  418. $this->validate($request, [
  419. 'q' => 'required|string|min:1|max:255',
  420. 'limit' => 'nullable|integer|min:1|max:40',
  421. 'resolve' => 'nullable'
  422. ]);
  423. $user = $request->user();
  424. $query = $request->input('q');
  425. $limit = $request->input('limit') ?? 20;
  426. $resolve = (bool) $request->input('resolve', false);
  427. $q = '%' . $query . '%';
  428. $profiles = Profile::whereNull('status')
  429. ->where('username', 'like', $q)
  430. ->orWhere('name', 'like', $q)
  431. ->limit($limit)
  432. ->get();
  433. $resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
  434. $res = $this->fractal->createData($resource)->toArray();
  435. return response()->json($res);
  436. }
  437. /**
  438. * GET /api/v1/blocks
  439. *
  440. *
  441. *
  442. * @return \App\Transformer\Api\AccountTransformer
  443. */
  444. public function accountBlocks(Request $request)
  445. {
  446. abort_if(!$request->user(), 403);
  447. $this->validate($request, [
  448. 'limit' => 'nullable|integer|min:1|max:40',
  449. 'page' => 'nullable|integer|min:1|max:10'
  450. ]);
  451. $user = $request->user();
  452. $limit = $request->input('limit') ?? 40;
  453. $blocked = UserFilter::select('filterable_id','filterable_type','filter_type','user_id')
  454. ->whereUserId($user->profile_id)
  455. ->whereFilterableType('App\Profile')
  456. ->whereFilterType('block')
  457. ->simplePaginate($limit)
  458. ->pluck('filterable_id');
  459. $profiles = Profile::findOrFail($blocked);
  460. $resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
  461. $res = $this->fractal->createData($resource)->toArray();
  462. return response()->json($res);
  463. }
  464. /**
  465. * POST /api/v1/accounts/{id}/block
  466. *
  467. * @param integer $id
  468. *
  469. * @return \App\Transformer\Api\RelationshipTransformer
  470. */
  471. public function accountBlockById(Request $request, $id)
  472. {
  473. abort_if(!$request->user(), 403);
  474. $user = $request->user();
  475. $pid = $user->profile_id ?? $user->profile->id;
  476. if($id == $pid) {
  477. abort(400, 'You cannot block yourself');
  478. }
  479. $profile = Profile::findOrFail($id);
  480. Follower::whereProfileId($profile->id)->whereFollowingId($pid)->delete();
  481. Follower::whereProfileId($pid)->whereFollowingId($profile->id)->delete();
  482. Notification::whereProfileId($pid)->whereActorId($profile->id)->delete();
  483. $filter = UserFilter::firstOrCreate([
  484. 'user_id' => $pid,
  485. 'filterable_id' => $profile->id,
  486. 'filterable_type' => 'App\Profile',
  487. 'filter_type' => 'block',
  488. ]);
  489. Cache::forget("user:filter:list:$pid");
  490. Cache::forget("api:local:exp:rec:$pid");
  491. $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
  492. $res = $this->fractal->createData($resource)->toArray();
  493. return response()->json($res);
  494. }
  495. /**
  496. * POST /api/v1/accounts/{id}/unblock
  497. *
  498. * @param integer $id
  499. *
  500. * @return \App\Transformer\Api\RelationshipTransformer
  501. */
  502. public function accountUnblockById(Request $request, $id)
  503. {
  504. abort_if(!$request->user(), 403);
  505. $user = $request->user();
  506. $pid = $user->profile_id ?? $user->profile->id;
  507. if($id == $pid) {
  508. abort(400, 'You cannot unblock yourself');
  509. }
  510. $profile = Profile::findOrFail($id);
  511. UserFilter::whereUserId($pid)
  512. ->whereFilterableId($profile->id)
  513. ->whereFilterableType('App\Profile')
  514. ->whereFilterType('block')
  515. ->delete();
  516. Cache::forget("user:filter:list:$pid");
  517. Cache::forget("api:local:exp:rec:$pid");
  518. $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
  519. $res = $this->fractal->createData($resource)->toArray();
  520. return response()->json($res);
  521. }
  522. /**
  523. * GET /api/v1/custom_emojis
  524. *
  525. * Return empty array, we don't support custom emoji
  526. *
  527. * @return array
  528. */
  529. public function customEmojis()
  530. {
  531. return response()->json([]);
  532. }
  533. /**
  534. * GET /api/v1/domain_blocks
  535. *
  536. * Return empty array
  537. *
  538. * @return array
  539. */
  540. public function accountDomainBlocks(Request $request)
  541. {
  542. abort_if(!$request->user(), 403);
  543. return response()->json([]);
  544. }
  545. /**
  546. * GET /api/v1/endorsements
  547. *
  548. * Return empty array
  549. *
  550. * @return array
  551. */
  552. public function accountEndorsements(Request $request)
  553. {
  554. abort_if(!$request->user(), 403);
  555. return response()->json([]);
  556. }
  557. /**
  558. * GET /api/v1/favourites
  559. *
  560. * Returns collection of liked statuses
  561. *
  562. * @return \App\Transformer\Api\StatusTransformer
  563. */
  564. public function accountFavourites(Request $request)
  565. {
  566. abort_if(!$request->user(), 403);
  567. $user = $request->user();
  568. $limit = $request->input('limit') ?? 20;
  569. $favourites = Like::whereProfileId($user->profile_id)
  570. ->latest()
  571. ->simplePaginate($limit)
  572. ->pluck('status_id');
  573. $statuses = Status::findOrFail($favourites);
  574. $resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
  575. $res = $this->fractal->createData($resource)->toArray();
  576. return response()->json($res);
  577. }
  578. /**
  579. * POST /api/v1/statuses/{id}/favourite
  580. *
  581. * @param integer $id
  582. *
  583. * @return \App\Transformer\Api\StatusTransformer
  584. */
  585. public function statusFavouriteById(Request $request, $id)
  586. {
  587. abort_if(!$request->user(), 403);
  588. $user = $request->user();
  589. $status = Status::findOrFail($id);
  590. $like = Like::firstOrCreate([
  591. 'profile_id' => $user->profile_id,
  592. 'status_id' => $status->id
  593. ]);
  594. if($like->wasRecentlyCreated == true) {
  595. LikePipeline::dispatch($like);
  596. }
  597. $resource = new Fractal\Resource\Item($status, new StatusTransformer());
  598. $res = $this->fractal->createData($resource)->toArray();
  599. return response()->json($res);
  600. }
  601. /**
  602. * POST /api/v1/statuses/{id}/unfavourite
  603. *
  604. * @param integer $id
  605. *
  606. * @return \App\Transformer\Api\StatusTransformer
  607. */
  608. public function statusUnfavouriteById(Request $request, $id)
  609. {
  610. abort_if(!$request->user(), 403);
  611. $user = $request->user();
  612. $status = Status::findOrFail($id);
  613. $like = Like::whereProfileId($user->profile_id)
  614. ->whereStatusId($status->id)
  615. ->first();
  616. if($like) {
  617. $like->delete();
  618. }
  619. $resource = new Fractal\Resource\Item($status, new StatusTransformer());
  620. $res = $this->fractal->createData($resource)->toArray();
  621. return response()->json($res);
  622. }
  623. /**
  624. * GET /api/v1/filters
  625. *
  626. * Return empty response since we filter server side
  627. *
  628. * @return array
  629. */
  630. public function accountFilters(Request $request)
  631. {
  632. abort_if(!$request->user(), 403);
  633. return response()->json([]);
  634. }
  635. /**
  636. * GET /api/v1/follow_requests
  637. *
  638. * Return array of Accounts that have sent follow requests
  639. *
  640. * @return \App\Transformer\Api\AccountTransformer
  641. */
  642. public function accountFollowRequests(Request $request)
  643. {
  644. abort_if(!$request->user(), 403);
  645. $user = $request->user();
  646. $followRequests = FollowRequest::whereFollowingId($user->profile->id)->pluck('follower_id');
  647. $profiles = Profile::find($followRequests);
  648. $resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
  649. $res = $this->fractal->createData($resource)->toArray();
  650. return response()->json($res);
  651. }
  652. /**
  653. * POST /api/v1/follow_requests/{id}/authorize
  654. *
  655. * @param integer $id
  656. *
  657. * @return null
  658. */
  659. public function accountFollowRequestAccept(Request $request, $id)
  660. {
  661. abort_if(!$request->user(), 403);
  662. // todo
  663. return response()->json([]);
  664. }
  665. /**
  666. * POST /api/v1/follow_requests/{id}/reject
  667. *
  668. * @param integer $id
  669. *
  670. * @return null
  671. */
  672. public function accountFollowRequestReject(Request $request, $id)
  673. {
  674. abort_if(!$request->user(), 403);
  675. // todo
  676. return response()->json([]);
  677. }
  678. /**
  679. * GET /api/v1/suggestions
  680. *
  681. * Return empty array as we don't support suggestions
  682. *
  683. * @return null
  684. */
  685. public function accountSuggestions(Request $request)
  686. {
  687. abort_if(!$request->user(), 403);
  688. // todo
  689. return response()->json([]);
  690. }
  691. public function statusById(Request $request, $id)
  692. {
  693. $status = Status::whereVisibility('public')->findOrFail($id);
  694. $resource = new Fractal\Resource\Item($status, new StatusTransformer());
  695. $res = $this->fractal->createData($resource)->toArray();
  696. return response()->json($res);
  697. }
  698. public function instance(Request $request)
  699. {
  700. $res = [
  701. 'description' => 'Pixelfed - Photo sharing for everyone',
  702. 'email' => config('instance.email'),
  703. 'languages' => ['en'],
  704. 'max_toot_chars' => config('pixelfed.max_caption_length'),
  705. 'registrations' => config('pixelfed.open_registration'),
  706. 'stats' => [
  707. 'user_count' => 0,
  708. 'status_count' => 0,
  709. 'domain_count' => 0
  710. ],
  711. 'thumbnail' => config('app.url') . '/img/pixelfed-icon-color.png',
  712. 'title' => 'Pixelfed (' . config('pixelfed.domain.app') . ')',
  713. 'uri' => config('app.url'),
  714. 'urls' => [],
  715. 'version' => '2.7.2 (compatible; Pixelfed ' . config('pixelfed.version') . ')'
  716. ];
  717. return response()->json($res, 200, [], JSON_PRETTY_PRINT);
  718. }
  719. public function context(Request $request)
  720. {
  721. // todo
  722. $res = [
  723. 'ancestors' => [],
  724. 'descendants' => []
  725. ];
  726. return response()->json($res);
  727. }
  728. public function createStatus(Request $request)
  729. {
  730. abort_if(!$request->user(), 403);
  731. $this->validate($request, [
  732. 'status' => 'string',
  733. 'media_ids' => 'array',
  734. 'media_ids.*' => 'integer|min:1',
  735. 'sensitive' => 'nullable|boolean',
  736. 'visibility' => 'string|in:private,unlisted,public',
  737. 'in_reply_to_id' => 'integer'
  738. ]);
  739. if(!$request->filled('media_ids') && !$request->filled('in_reply_to_id')) {
  740. abort(403, 'Empty statuses are not allowed');
  741. }
  742. }
  743. }