ApiV1Dot1Controller.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. <?php
  2. namespace App\Http\Controllers\Api;
  3. use Cache;
  4. use DB;
  5. use App\Http\Controllers\Controller;
  6. use Illuminate\Http\Request;
  7. use League\Fractal;
  8. use League\Fractal\Serializer\ArraySerializer;
  9. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  10. use App\AccountLog;
  11. use App\EmailVerification;
  12. use App\Place;
  13. use App\Status;
  14. use App\Report;
  15. use App\Profile;
  16. use App\StatusArchived;
  17. use App\User;
  18. use App\Services\AccountService;
  19. use App\Services\StatusService;
  20. use App\Services\ProfileStatusService;
  21. use App\Services\PublicTimelineService;
  22. use App\Services\NetworkTimelineService;
  23. use App\Util\Lexer\RestrictedNames;
  24. use App\Services\BouncerService;
  25. use App\Services\EmailService;
  26. use Illuminate\Support\Str;
  27. use Illuminate\Support\Facades\Hash;
  28. use Jenssegers\Agent\Agent;
  29. use Mail;
  30. use App\Mail\PasswordChange;
  31. use App\Mail\ConfirmAppEmail;
  32. use App\Http\Resources\StatusStateless;
  33. use App\Jobs\StatusPipeline\StatusDelete;
  34. use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail;
  35. class ApiV1Dot1Controller extends Controller
  36. {
  37. protected $fractal;
  38. public function __construct()
  39. {
  40. $this->fractal = new Fractal\Manager();
  41. $this->fractal->setSerializer(new ArraySerializer());
  42. }
  43. public function json($res, $code = 200, $headers = [])
  44. {
  45. return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES);
  46. }
  47. public function error($msg, $code = 400, $extra = [], $headers = [])
  48. {
  49. $res = [
  50. "msg" => $msg,
  51. "code" => $code
  52. ];
  53. return response()->json(array_merge($res, $extra), $code, $headers, JSON_UNESCAPED_SLASHES);
  54. }
  55. public function report(Request $request)
  56. {
  57. $user = $request->user();
  58. abort_if(!$user, 403);
  59. abort_if($user->status != null, 403);
  60. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  61. abort_if(BouncerService::checkIp($request->ip()), 404);
  62. }
  63. $report_type = $request->input('report_type');
  64. $object_id = $request->input('object_id');
  65. $object_type = $request->input('object_type');
  66. $types = [
  67. 'spam',
  68. 'sensitive',
  69. 'abusive',
  70. 'underage',
  71. 'violence',
  72. 'copyright',
  73. 'impersonation',
  74. 'scam',
  75. 'terrorism'
  76. ];
  77. if (!$report_type || !$object_id || !$object_type) {
  78. return $this->error("Invalid or missing parameters", 400, ["error_code" => "ERROR_INVALID_PARAMS"]);
  79. }
  80. if (!in_array($report_type, $types)) {
  81. return $this->error("Invalid report type", 400, ["error_code" => "ERROR_TYPE_INVALID"]);
  82. }
  83. if ($object_type === "user" && $object_id == $user->profile_id) {
  84. return $this->error("Cannot self report", 400, ["error_code" => "ERROR_NO_SELF_REPORTS"]);
  85. }
  86. $rpid = null;
  87. switch ($object_type) {
  88. case 'post':
  89. $object = Status::find($object_id);
  90. if (!$object) {
  91. return $this->error("Invalid object id", 400, ["error_code" => "ERROR_INVALID_OBJECT_ID"]);
  92. }
  93. $object_type = 'App\Status';
  94. $exists = Report::whereUserId($user->id)
  95. ->whereObjectId($object->id)
  96. ->whereObjectType('App\Status')
  97. ->count();
  98. $rpid = $object->profile_id;
  99. break;
  100. case 'user':
  101. $object = Profile::find($object_id);
  102. if (!$object) {
  103. return $this->error("Invalid object id", 400, ["error_code" => "ERROR_INVALID_OBJECT_ID"]);
  104. }
  105. $object_type = 'App\Profile';
  106. $exists = Report::whereUserId($user->id)
  107. ->whereObjectId($object->id)
  108. ->whereObjectType('App\Profile')
  109. ->count();
  110. $rpid = $object->id;
  111. break;
  112. default:
  113. return $this->error("Invalid report type", 400, ["error_code" => "ERROR_REPORT_OBJECT_TYPE_INVALID"]);
  114. break;
  115. }
  116. if ($exists !== 0) {
  117. return $this->error("Duplicate report", 400, ["error_code" => "ERROR_REPORT_DUPLICATE"]);
  118. }
  119. if ($object->profile_id == $user->profile_id) {
  120. return $this->error("Cannot self report", 400, ["error_code" => "ERROR_NO_SELF_REPORTS"]);
  121. }
  122. $report = new Report;
  123. $report->profile_id = $user->profile_id;
  124. $report->user_id = $user->id;
  125. $report->object_id = $object->id;
  126. $report->object_type = $object_type;
  127. $report->reported_profile_id = $rpid;
  128. $report->type = $report_type;
  129. $report->save();
  130. if(config('instance.reports.email.enabled')) {
  131. ReportNotifyAdminViaEmail::dispatch($report)->onQueue('default');
  132. }
  133. $res = [
  134. "msg" => "Successfully sent report",
  135. "code" => 200
  136. ];
  137. return $this->json($res);
  138. }
  139. /**
  140. * DELETE /api/v1.1/accounts/avatar
  141. *
  142. * @return \App\Transformer\Api\AccountTransformer
  143. */
  144. public function deleteAvatar(Request $request)
  145. {
  146. $user = $request->user();
  147. abort_if(!$user, 403);
  148. abort_if($user->status != null, 403);
  149. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  150. abort_if(BouncerService::checkIp($request->ip()), 404);
  151. }
  152. $avatar = $user->profile->avatar;
  153. if( $avatar->media_path == 'public/avatars/default.png' ||
  154. $avatar->media_path == 'public/avatars/default.jpg'
  155. ) {
  156. return AccountService::get($user->profile_id);
  157. }
  158. if(is_file(storage_path('app/' . $avatar->media_path))) {
  159. @unlink(storage_path('app/' . $avatar->media_path));
  160. }
  161. $avatar->media_path = 'public/avatars/default.jpg';
  162. $avatar->change_count = $avatar->change_count + 1;
  163. $avatar->save();
  164. Cache::forget('avatar:' . $user->profile_id);
  165. Cache::forget("avatar:{$user->profile_id}");
  166. Cache::forget('user:account:id:'.$user->id);
  167. AccountService::del($user->profile_id);
  168. return AccountService::get($user->profile_id);
  169. }
  170. /**
  171. * GET /api/v1.1/accounts/{id}/posts
  172. *
  173. * @return \App\Transformer\Api\StatusTransformer
  174. */
  175. public function accountPosts(Request $request, $id)
  176. {
  177. $user = $request->user();
  178. abort_if(!$user, 403);
  179. abort_if($user->status != null, 403);
  180. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  181. abort_if(BouncerService::checkIp($request->ip()), 404);
  182. }
  183. $account = AccountService::get($id);
  184. if(!$account || $account['username'] !== $request->input('username')) {
  185. return $this->json([]);
  186. }
  187. $posts = ProfileStatusService::get($id);
  188. if(!$posts) {
  189. return $this->json([]);
  190. }
  191. $res = collect($posts)
  192. ->map(function($id) {
  193. return StatusService::get($id);
  194. })
  195. ->filter(function($post) {
  196. return $post && isset($post['account']);
  197. })
  198. ->toArray();
  199. return $this->json($res);
  200. }
  201. /**
  202. * POST /api/v1.1/accounts/change-password
  203. *
  204. * @return \App\Transformer\Api\AccountTransformer
  205. */
  206. public function accountChangePassword(Request $request)
  207. {
  208. $user = $request->user();
  209. abort_if(!$user, 403);
  210. abort_if($user->status != null, 403);
  211. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  212. abort_if(BouncerService::checkIp($request->ip()), 404);
  213. }
  214. $this->validate($request, [
  215. 'current_password' => 'bail|required|current_password',
  216. 'new_password' => 'required|min:' . config('pixelfed.min_password_length', 8),
  217. 'confirm_password' => 'required|same:new_password'
  218. ],[
  219. 'current_password' => 'The password you entered is incorrect'
  220. ]);
  221. $user->password = bcrypt($request->input('new_password'));
  222. $user->save();
  223. $log = new AccountLog;
  224. $log->user_id = $user->id;
  225. $log->item_id = $user->id;
  226. $log->item_type = 'App\User';
  227. $log->action = 'account.edit.password';
  228. $log->message = 'Password changed';
  229. $log->link = null;
  230. $log->ip_address = $request->ip();
  231. $log->user_agent = $request->userAgent();
  232. $log->save();
  233. Mail::to($request->user())->send(new PasswordChange($user));
  234. return $this->json(AccountService::get($user->profile_id));
  235. }
  236. /**
  237. * GET /api/v1.1/accounts/login-activity
  238. *
  239. * @return array
  240. */
  241. public function accountLoginActivity(Request $request)
  242. {
  243. $user = $request->user();
  244. abort_if(!$user, 403);
  245. abort_if($user->status != null, 403);
  246. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  247. abort_if(BouncerService::checkIp($request->ip()), 404);
  248. }
  249. $agent = new Agent();
  250. $currentIp = $request->ip();
  251. $activity = AccountLog::whereUserId($user->id)
  252. ->whereAction('auth.login')
  253. ->orderBy('created_at', 'desc')
  254. ->groupBy('ip_address')
  255. ->limit(10)
  256. ->get()
  257. ->map(function($item) use($agent, $currentIp) {
  258. $agent->setUserAgent($item->user_agent);
  259. return [
  260. 'id' => $item->id,
  261. 'action' => $item->action,
  262. 'ip' => $item->ip_address,
  263. 'ip_current' => $item->ip_address === $currentIp,
  264. 'is_mobile' => $agent->isMobile(),
  265. 'device' => $agent->device(),
  266. 'browser' => $agent->browser(),
  267. 'platform' => $agent->platform(),
  268. 'created_at' => $item->created_at->format('c')
  269. ];
  270. });
  271. return $this->json($activity);
  272. }
  273. /**
  274. * GET /api/v1.1/accounts/two-factor
  275. *
  276. * @return array
  277. */
  278. public function accountTwoFactor(Request $request)
  279. {
  280. $user = $request->user();
  281. abort_if(!$user, 403);
  282. abort_if($user->status != null, 403);
  283. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  284. abort_if(BouncerService::checkIp($request->ip()), 404);
  285. }
  286. $res = [
  287. 'active' => (bool) $user->{'2fa_enabled'},
  288. 'setup_at' => $user->{'2fa_setup_at'}
  289. ];
  290. return $this->json($res);
  291. }
  292. /**
  293. * GET /api/v1.1/accounts/emails-from-pixelfed
  294. *
  295. * @return array
  296. */
  297. public function accountEmailsFromPixelfed(Request $request)
  298. {
  299. $user = $request->user();
  300. abort_if(!$user, 403);
  301. abort_if($user->status != null, 403);
  302. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  303. abort_if(BouncerService::checkIp($request->ip()), 404);
  304. }
  305. $from = config('mail.from.address');
  306. $emailVerifications = EmailVerification::whereUserId($user->id)
  307. ->orderByDesc('id')
  308. ->where('created_at', '>', now()->subDays(14))
  309. ->limit(10)
  310. ->get()
  311. ->map(function($mail) use($user, $from) {
  312. return [
  313. 'type' => 'Email Verification',
  314. 'subject' => 'Confirm Email',
  315. 'to_address' => $user->email,
  316. 'from_address' => $from,
  317. 'created_at' => str_replace('@', 'at', $mail->created_at->format('M j, Y @ g:i:s A'))
  318. ];
  319. })
  320. ->toArray();
  321. $passwordResets = DB::table('password_resets')
  322. ->whereEmail($user->email)
  323. ->where('created_at', '>', now()->subDays(14))
  324. ->orderByDesc('created_at')
  325. ->limit(10)
  326. ->get()
  327. ->map(function($mail) use($user, $from) {
  328. return [
  329. 'type' => 'Password Reset',
  330. 'subject' => 'Reset Password Notification',
  331. 'to_address' => $user->email,
  332. 'from_address' => $from,
  333. 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A'))
  334. ];
  335. })
  336. ->toArray();
  337. $passwordChanges = AccountLog::whereUserId($user->id)
  338. ->whereAction('account.edit.password')
  339. ->where('created_at', '>', now()->subDays(14))
  340. ->orderByDesc('created_at')
  341. ->limit(10)
  342. ->get()
  343. ->map(function($mail) use($user, $from) {
  344. return [
  345. 'type' => 'Password Change',
  346. 'subject' => 'Password Change',
  347. 'to_address' => $user->email,
  348. 'from_address' => $from,
  349. 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A'))
  350. ];
  351. })
  352. ->toArray();
  353. $res = collect([])
  354. ->merge($emailVerifications)
  355. ->merge($passwordResets)
  356. ->merge($passwordChanges)
  357. ->sortByDesc('created_at')
  358. ->values();
  359. return $this->json($res);
  360. }
  361. /**
  362. * GET /api/v1.1/accounts/apps-and-applications
  363. *
  364. * @return array
  365. */
  366. public function accountApps(Request $request)
  367. {
  368. $user = $request->user();
  369. abort_if(!$user, 403);
  370. abort_if($user->status != null, 403);
  371. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  372. abort_if(BouncerService::checkIp($request->ip()), 404);
  373. }
  374. $res = $user->tokens->sortByDesc('created_at')->take(10)->map(function($token, $key) use($request) {
  375. return [
  376. 'id' => $token->id,
  377. 'current_session' => $request->user()->token()->id == $token->id,
  378. 'name' => $token->client->name,
  379. 'scopes' => $token->scopes,
  380. 'revoked' => $token->revoked,
  381. 'created_at' => str_replace('@', 'at', now()->parse($token->created_at)->format('M j, Y @ g:i:s A')),
  382. 'expires_at' => str_replace('@', 'at', now()->parse($token->expires_at)->format('M j, Y @ g:i:s A'))
  383. ];
  384. });
  385. return $this->json($res);
  386. }
  387. public function inAppRegistrationPreFlightCheck(Request $request)
  388. {
  389. return [
  390. 'open' => config_cache('pixelfed.open_registration'),
  391. 'iara' => config('pixelfed.allow_app_registration')
  392. ];
  393. }
  394. public function inAppRegistration(Request $request)
  395. {
  396. abort_if($request->user(), 404);
  397. abort_unless(config_cache('pixelfed.open_registration'), 404);
  398. abort_unless(config('pixelfed.allow_app_registration'), 404);
  399. abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
  400. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  401. abort_if(BouncerService::checkIp($request->ip()), 404);
  402. }
  403. $this->validate($request, [
  404. 'email' => [
  405. 'required',
  406. 'string',
  407. 'email',
  408. 'max:255',
  409. 'unique:users',
  410. function ($attribute, $value, $fail) {
  411. $banned = EmailService::isBanned($value);
  412. if($banned) {
  413. return $fail('Email is invalid.');
  414. }
  415. },
  416. ],
  417. 'username' => [
  418. 'required',
  419. 'min:2',
  420. 'max:15',
  421. 'unique:users',
  422. function ($attribute, $value, $fail) {
  423. $dash = substr_count($value, '-');
  424. $underscore = substr_count($value, '_');
  425. $period = substr_count($value, '.');
  426. if(ends_with($value, ['.php', '.js', '.css'])) {
  427. return $fail('Username is invalid.');
  428. }
  429. if(($dash + $underscore + $period) > 1) {
  430. return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
  431. }
  432. if (!ctype_alnum($value[0])) {
  433. return $fail('Username is invalid. Must start with a letter or number.');
  434. }
  435. if (!ctype_alnum($value[strlen($value) - 1])) {
  436. return $fail('Username is invalid. Must end with a letter or number.');
  437. }
  438. $val = str_replace(['_', '.', '-'], '', $value);
  439. if(!ctype_alnum($val)) {
  440. return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
  441. }
  442. $restricted = RestrictedNames::get();
  443. if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
  444. return $fail('Username cannot be used.');
  445. }
  446. },
  447. ],
  448. 'password' => 'required|string|min:8',
  449. ]);
  450. $email = $request->input('email');
  451. $username = $request->input('username');
  452. $password = $request->input('password');
  453. if(config('database.default') == 'pgsql') {
  454. $username = strtolower($username);
  455. $email = strtolower($email);
  456. }
  457. $user = new User;
  458. $user->name = $username;
  459. $user->username = $username;
  460. $user->email = $email;
  461. $user->password = Hash::make($password);
  462. $user->register_source = 'app';
  463. $user->app_register_ip = $request->ip();
  464. $user->app_register_token = Str::random(32);
  465. $user->save();
  466. $rtoken = Str::random(mt_rand(64, 70));
  467. $verify = new EmailVerification();
  468. $verify->user_id = $user->id;
  469. $verify->email = $user->email;
  470. $verify->user_token = $user->app_register_token;
  471. $verify->random_token = $rtoken;
  472. $verify->save();
  473. $appUrl = url('/api/v1.1/auth/iarer?ut=' . $user->app_register_token . '&rt=' . $rtoken);
  474. Mail::to($user->email)->send(new ConfirmAppEmail($verify, $appUrl));
  475. return response()->json([
  476. 'success' => true,
  477. ]);
  478. }
  479. public function inAppRegistrationEmailRedirect(Request $request)
  480. {
  481. $this->validate($request, [
  482. 'ut' => 'required',
  483. 'rt' => 'required'
  484. ]);
  485. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  486. abort_if(BouncerService::checkIp($request->ip()), 404);
  487. }
  488. $ut = $request->input('ut');
  489. $rt = $request->input('rt');
  490. $url = 'pixelfed://confirm-account/'. $ut . '?rt=' . $rt;
  491. return redirect()->away($url);
  492. }
  493. public function inAppRegistrationConfirm(Request $request)
  494. {
  495. abort_if($request->user(), 404);
  496. abort_unless(config_cache('pixelfed.open_registration'), 404);
  497. abort_unless(config('pixelfed.allow_app_registration'), 404);
  498. abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
  499. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  500. abort_if(BouncerService::checkIp($request->ip()), 404);
  501. }
  502. $this->validate($request, [
  503. 'user_token' => 'required',
  504. 'random_token' => 'required',
  505. 'email' => 'required'
  506. ]);
  507. $verify = EmailVerification::whereEmail($request->input('email'))
  508. ->whereUserToken($request->input('user_token'))
  509. ->whereRandomToken($request->input('random_token'))
  510. ->first();
  511. if(!$verify) {
  512. return response()->json(['error' => 'Invalid tokens'], 403);
  513. }
  514. if($verify->created_at->lt(now()->subHours(24))) {
  515. $verify->delete();
  516. return response()->json(['error' => 'Invalid tokens'], 403);
  517. }
  518. $user = User::findOrFail($verify->user_id);
  519. $user->email_verified_at = now();
  520. $user->last_active_at = now();
  521. $user->save();
  522. $token = $user->createToken('Pixelfed');
  523. return response()->json([
  524. 'access_token' => $token->accessToken
  525. ]);
  526. }
  527. public function archive(Request $request, $id)
  528. {
  529. abort_if(!$request->user(), 403);
  530. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  531. abort_if(BouncerService::checkIp($request->ip()), 404);
  532. }
  533. $status = Status::whereNull('in_reply_to_id')
  534. ->whereNull('reblog_of_id')
  535. ->whereProfileId($request->user()->profile_id)
  536. ->findOrFail($id);
  537. if($status->scope === 'archived') {
  538. return [200];
  539. }
  540. $archive = new StatusArchived;
  541. $archive->status_id = $status->id;
  542. $archive->profile_id = $status->profile_id;
  543. $archive->original_scope = $status->scope;
  544. $archive->save();
  545. $status->scope = 'archived';
  546. $status->visibility = 'draft';
  547. $status->save();
  548. StatusService::del($status->id, true);
  549. AccountService::syncPostCount($status->profile_id);
  550. return [200];
  551. }
  552. public function unarchive(Request $request, $id)
  553. {
  554. abort_if(!$request->user(), 403);
  555. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  556. abort_if(BouncerService::checkIp($request->ip()), 404);
  557. }
  558. $status = Status::whereNull('in_reply_to_id')
  559. ->whereNull('reblog_of_id')
  560. ->whereProfileId($request->user()->profile_id)
  561. ->findOrFail($id);
  562. if($status->scope !== 'archived') {
  563. return [200];
  564. }
  565. $archive = StatusArchived::whereStatusId($status->id)
  566. ->whereProfileId($status->profile_id)
  567. ->firstOrFail();
  568. $status->scope = $archive->original_scope;
  569. $status->visibility = $archive->original_scope;
  570. $status->save();
  571. $archive->delete();
  572. StatusService::del($status->id, true);
  573. AccountService::syncPostCount($status->profile_id);
  574. return [200];
  575. }
  576. public function archivedPosts(Request $request)
  577. {
  578. abort_if(!$request->user(), 403);
  579. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  580. abort_if(BouncerService::checkIp($request->ip()), 404);
  581. }
  582. $statuses = Status::whereProfileId($request->user()->profile_id)
  583. ->whereScope('archived')
  584. ->orderByDesc('id')
  585. ->cursorPaginate(10);
  586. return StatusStateless::collection($statuses);
  587. }
  588. public function placesById(Request $request, $id, $slug)
  589. {
  590. abort_if(!$request->user(), 403);
  591. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  592. abort_if(BouncerService::checkIp($request->ip()), 404);
  593. }
  594. $place = Place::whereSlug($slug)->findOrFail($id);
  595. $posts = Cache::remember('pf-api:v1.1:places-by-id:' . $place->id, 3600, function() use($place) {
  596. return Status::wherePlaceId($place->id)
  597. ->whereNull('uri')
  598. ->whereScope('public')
  599. ->orderByDesc('created_at')
  600. ->limit(60)
  601. ->pluck('id');
  602. });
  603. $posts = $posts->map(function($id) {
  604. return StatusService::get($id);
  605. })
  606. ->filter()
  607. ->values();
  608. return [
  609. 'place' =>
  610. [
  611. 'id' => $place->id,
  612. 'name' => $place->name,
  613. 'slug' => $place->slug,
  614. 'country' => $place->country,
  615. 'lat' => $place->lat,
  616. 'long' => $place->long
  617. ],
  618. 'posts' => $posts];
  619. }
  620. public function moderatePost(Request $request, $id)
  621. {
  622. abort_if(!$request->user(), 403);
  623. abort_if($request->user()->is_admin != true, 403);
  624. if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  625. abort_if(BouncerService::checkIp($request->ip()), 404);
  626. }
  627. $this->validate($request, [
  628. 'action' => 'required|in:cw,mark-public,mark-unlisted,mark-private,mark-spammer,delete'
  629. ]);
  630. $action = $request->input('action');
  631. $status = Status::find($id);
  632. if(!$status) {
  633. return response()->json(['error' => 'Cannot find status'], 400);
  634. }
  635. if($status->uri == null) {
  636. if($status->profile->user && $status->profile->user->is_admin) {
  637. return response()->json(['error' => 'Cannot moderate admin accounts'], 400);
  638. }
  639. }
  640. if($action == 'mark-spammer') {
  641. $status->profile->update([
  642. 'unlisted' => true,
  643. 'cw' => true,
  644. 'no_autolink' => true
  645. ]);
  646. Status::whereProfileId($status->profile_id)
  647. ->get()
  648. ->each(function($s) {
  649. if(in_array($s->scope, ['public', 'unlisted'])) {
  650. $s->scope = 'private';
  651. $s->visibility = 'private';
  652. }
  653. $s->is_nsfw = true;
  654. $s->save();
  655. StatusService::del($s->id, true);
  656. });
  657. Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $status->profile_id);
  658. Cache::forget('pf:bouncer_v0:recent_by_pid:' . $status->profile_id);
  659. Cache::forget('admin-dash:reports:spam-count');
  660. } else if ($action == 'cw') {
  661. $state = $status->is_nsfw;
  662. $status->is_nsfw = !$state;
  663. $status->save();
  664. StatusService::del($status->id);
  665. } else if ($action == 'mark-public') {
  666. $state = $status->scope;
  667. $status->scope = 'public';
  668. $status->visibility = 'public';
  669. $status->save();
  670. StatusService::del($status->id, true);
  671. if($state !== 'public') {
  672. if($status->uri) {
  673. NetworkTimelineService::add($status->id);
  674. } else {
  675. PublicTimelineService::add($status->id);
  676. }
  677. }
  678. } else if ($action == 'mark-unlisted') {
  679. $state = $status->scope;
  680. $status->scope = 'unlisted';
  681. $status->visibility = 'unlisted';
  682. $status->save();
  683. StatusService::del($status->id);
  684. if($state == 'public') {
  685. PublicTimelineService::del($status->id);
  686. NetworkTimelineService::del($status->id);
  687. }
  688. } else if ($action == 'mark-private') {
  689. $state = $status->scope;
  690. $status->scope = 'private';
  691. $status->visibility = 'private';
  692. $status->save();
  693. StatusService::del($status->id);
  694. if($state == 'public') {
  695. PublicTimelineService::del($status->id);
  696. NetworkTimelineService::del($status->id);
  697. }
  698. } else if ($action == 'delete') {
  699. PublicTimelineService::del($status->id);
  700. NetworkTimelineService::del($status->id);
  701. Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
  702. Cache::forget('profile:status_count:' . $status->profile_id);
  703. Cache::forget('profile:embed:' . $status->profile_id);
  704. StatusService::del($status->id, true);
  705. Cache::forget('profile:status_count:'.$status->profile_id);
  706. StatusDelete::dispatch($status);
  707. return [];
  708. }
  709. Cache::forget('_api:statuses:recent_9:'.$status->profile_id);
  710. return StatusService::get($status->id, false);
  711. }
  712. }