AdminController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\{
  4. AccountInterstitial,
  5. Contact,
  6. Hashtag,
  7. Newsroom,
  8. OauthClient,
  9. Profile,
  10. Report,
  11. Status,
  12. Story,
  13. User
  14. };
  15. use DB, Cache;
  16. use Carbon\Carbon;
  17. use Illuminate\Http\Request;
  18. use App\Http\Controllers\Admin\{
  19. AdminDiscoverController,
  20. AdminInstanceController,
  21. AdminReportController,
  22. AdminMediaController,
  23. AdminSettingsController,
  24. AdminSupportController,
  25. AdminUserController
  26. };
  27. use Illuminate\Validation\Rule;
  28. use App\Services\AdminStatsService;
  29. use App\Services\StoryService;
  30. class AdminController extends Controller
  31. {
  32. use AdminReportController,
  33. AdminDiscoverController,
  34. AdminMediaController,
  35. AdminSettingsController,
  36. AdminInstanceController,
  37. AdminUserController;
  38. public function __construct()
  39. {
  40. $this->middleware('admin');
  41. $this->middleware('dangerzone');
  42. $this->middleware('twofactor');
  43. }
  44. public function home()
  45. {
  46. $data = AdminStatsService::get();
  47. return view('admin.home', compact('data'));
  48. }
  49. public function statuses(Request $request)
  50. {
  51. $statuses = Status::orderBy('id', 'desc')->simplePaginate(10);
  52. return view('admin.statuses.home', compact('statuses'));
  53. }
  54. public function showStatus(Request $request, $id)
  55. {
  56. $status = Status::findOrFail($id);
  57. return view('admin.statuses.show', compact('status'));
  58. }
  59. public function reports(Request $request)
  60. {
  61. $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
  62. $reports = Report::whereHas('status')
  63. ->whereHas('reportedUser')
  64. ->whereHas('reporter')
  65. ->orderBy('created_at','desc')
  66. ->when($filter, function($q, $filter) {
  67. return $filter == 'open' ?
  68. $q->whereNull('admin_seen') :
  69. $q->whereNotNull('admin_seen');
  70. })
  71. ->paginate(6);
  72. return view('admin.reports.home', compact('reports'));
  73. }
  74. public function showReport(Request $request, $id)
  75. {
  76. $report = Report::findOrFail($id);
  77. return view('admin.reports.show', compact('report'));
  78. }
  79. public function appeals(Request $request)
  80. {
  81. $appeals = AccountInterstitial::whereNotNull('appeal_requested_at')
  82. ->whereNull('appeal_handled_at')
  83. ->latest()
  84. ->paginate(6);
  85. return view('admin.reports.appeals', compact('appeals'));
  86. }
  87. public function showAppeal(Request $request, $id)
  88. {
  89. $appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
  90. ->whereNull('appeal_handled_at')
  91. ->findOrFail($id);
  92. $meta = json_decode($appeal->meta);
  93. return view('admin.reports.show_appeal', compact('appeal', 'meta'));
  94. }
  95. public function spam(Request $request)
  96. {
  97. $appeals = AccountInterstitial::whereType('post.autospam')
  98. ->whereNull('appeal_handled_at')
  99. ->latest()
  100. ->paginate(6);
  101. return view('admin.reports.spam', compact('appeals'));
  102. }
  103. public function showSpam(Request $request, $id)
  104. {
  105. $appeal = AccountInterstitial::whereType('post.autospam')
  106. ->whereNull('appeal_handled_at')
  107. ->findOrFail($id);
  108. $meta = json_decode($appeal->meta);
  109. return view('admin.reports.show_spam', compact('appeal', 'meta'));
  110. }
  111. public function updateSpam(Request $request, $id)
  112. {
  113. $this->validate($request, [
  114. 'action' => 'required|in:dismiss,approve'
  115. ]);
  116. $action = $request->input('action');
  117. $appeal = AccountInterstitial::whereType('post.autospam')
  118. ->whereNull('appeal_handled_at')
  119. ->findOrFail($id);
  120. $meta = json_decode($appeal->meta);
  121. if($action == 'dismiss') {
  122. $appeal->appeal_handled_at = now();
  123. $appeal->save();
  124. Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
  125. Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
  126. return redirect('/i/admin/reports/autospam');
  127. }
  128. $status = $appeal->status;
  129. $status->is_nsfw = $meta->is_nsfw;
  130. $status->scope = 'public';
  131. $status->visibility = 'public';
  132. $status->save();
  133. $appeal->appeal_handled_at = now();
  134. $appeal->save();
  135. Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
  136. Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
  137. return redirect('/i/admin/reports/autospam');
  138. }
  139. public function updateAppeal(Request $request, $id)
  140. {
  141. $this->validate($request, [
  142. 'action' => 'required|in:dismiss,approve'
  143. ]);
  144. $action = $request->input('action');
  145. $appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
  146. ->whereNull('appeal_handled_at')
  147. ->findOrFail($id);
  148. if($action == 'dismiss') {
  149. $appeal->appeal_handled_at = now();
  150. $appeal->save();
  151. return redirect('/i/admin/reports/appeals');
  152. }
  153. switch ($appeal->type) {
  154. case 'post.cw':
  155. $status = $appeal->status;
  156. $status->is_nsfw = false;
  157. $status->save();
  158. break;
  159. case 'post.unlist':
  160. $status = $appeal->status;
  161. $status->scope = 'public';
  162. $status->visibility = 'public';
  163. $status->save();
  164. break;
  165. default:
  166. # code...
  167. break;
  168. }
  169. $appeal->appeal_handled_at = now();
  170. $appeal->save();
  171. return redirect('/i/admin/reports/appeals');
  172. }
  173. public function profiles(Request $request)
  174. {
  175. $this->validate($request, [
  176. 'search' => 'nullable|string|max:250',
  177. 'filter' => [
  178. 'nullable',
  179. 'string',
  180. Rule::in(['all', 'local', 'remote'])
  181. ]
  182. ]);
  183. $search = $request->input('search');
  184. $filter = $request->input('filter');
  185. $limit = 12;
  186. $profiles = Profile::select('id','username')
  187. ->whereNull('status')
  188. ->when($search, function($q, $search) {
  189. return $q->where('username', 'like', "%$search%");
  190. })->when($filter, function($q, $filter) {
  191. if($filter == 'local') {
  192. return $q->whereNull('domain');
  193. }
  194. if($filter == 'remote') {
  195. return $q->whereNotNull('domain');
  196. }
  197. return $q;
  198. })->orderByDesc('id')
  199. ->simplePaginate($limit);
  200. return view('admin.profiles.home', compact('profiles'));
  201. }
  202. public function profileShow(Request $request, $id)
  203. {
  204. $profile = Profile::findOrFail($id);
  205. $user = $profile->user;
  206. return view('admin.profiles.edit', compact('profile', 'user'));
  207. }
  208. public function appsHome(Request $request)
  209. {
  210. $filter = $request->input('filter');
  211. if(in_array($filter, ['revoked'])) {
  212. $apps = OauthClient::with('user')
  213. ->whereNotNull('user_id')
  214. ->whereRevoked(true)
  215. ->orderByDesc('id')
  216. ->paginate(10);
  217. } else {
  218. $apps = OauthClient::with('user')
  219. ->whereNotNull('user_id')
  220. ->orderByDesc('id')
  221. ->paginate(10);
  222. }
  223. return view('admin.apps.home', compact('apps'));
  224. }
  225. public function hashtagsHome(Request $request)
  226. {
  227. $hashtags = Hashtag::orderByDesc('id')->paginate(10);
  228. return view('admin.hashtags.home', compact('hashtags'));
  229. }
  230. public function messagesHome(Request $request)
  231. {
  232. $messages = Contact::orderByDesc('id')->paginate(10);
  233. return view('admin.messages.home', compact('messages'));
  234. }
  235. public function messagesShow(Request $request, $id)
  236. {
  237. $message = Contact::findOrFail($id);
  238. return view('admin.messages.show', compact('message'));
  239. }
  240. public function messagesMarkRead(Request $request)
  241. {
  242. $this->validate($request, [
  243. 'id' => 'required|integer|min:1'
  244. ]);
  245. $id = $request->input('id');
  246. $message = Contact::findOrFail($id);
  247. if($message->read_at) {
  248. return;
  249. }
  250. $message->read_at = now();
  251. $message->save();
  252. return;
  253. }
  254. public function newsroomHome(Request $request)
  255. {
  256. $newsroom = Newsroom::latest()->paginate(10);
  257. return view('admin.newsroom.home', compact('newsroom'));
  258. }
  259. public function newsroomCreate(Request $request)
  260. {
  261. return view('admin.newsroom.create');
  262. }
  263. public function newsroomEdit(Request $request, $id)
  264. {
  265. $news = Newsroom::findOrFail($id);
  266. return view('admin.newsroom.edit', compact('news'));
  267. }
  268. public function newsroomDelete(Request $request, $id)
  269. {
  270. $news = Newsroom::findOrFail($id);
  271. $news->delete();
  272. return redirect('/i/admin/newsroom');
  273. }
  274. public function newsroomUpdate(Request $request, $id)
  275. {
  276. $this->validate($request, [
  277. 'title' => 'required|string|min:1|max:100',
  278. 'summary' => 'nullable|string|max:200',
  279. 'body' => 'nullable|string'
  280. ]);
  281. $changed = false;
  282. $changedFields = [];
  283. $news = Newsroom::findOrFail($id);
  284. $fields = [
  285. 'title' => 'string',
  286. 'summary' => 'string',
  287. 'body' => 'string',
  288. 'category' => 'string',
  289. 'show_timeline' => 'boolean',
  290. 'auth_only' => 'boolean',
  291. 'show_link' => 'boolean',
  292. 'force_modal' => 'boolean',
  293. 'published' => 'published'
  294. ];
  295. foreach($fields as $field => $type) {
  296. switch ($type) {
  297. case 'string':
  298. if($request->{$field} != $news->{$field}) {
  299. if($field == 'title') {
  300. $news->slug = str_slug($request->{$field});
  301. }
  302. $news->{$field} = $request->{$field};
  303. $changed = true;
  304. array_push($changedFields, $field);
  305. }
  306. break;
  307. case 'boolean':
  308. $state = $request->{$field} == 'on' ? true : false;
  309. if($state != $news->{$field}) {
  310. $news->{$field} = $state;
  311. $changed = true;
  312. array_push($changedFields, $field);
  313. }
  314. break;
  315. case 'published':
  316. $state = $request->{$field} == 'on' ? true : false;
  317. $published = $news->published_at != null;
  318. if($state != $published) {
  319. $news->published_at = $state ? now() : null;
  320. $changed = true;
  321. array_push($changedFields, $field);
  322. }
  323. break;
  324. }
  325. }
  326. if($changed) {
  327. $news->save();
  328. }
  329. $redirect = $news->published_at ? $news->permalink() : $news->editUrl();
  330. return redirect($redirect);
  331. }
  332. public function newsroomStore(Request $request)
  333. {
  334. $this->validate($request, [
  335. 'title' => 'required|string|min:1|max:100',
  336. 'summary' => 'nullable|string|max:200',
  337. 'body' => 'nullable|string'
  338. ]);
  339. $changed = false;
  340. $changedFields = [];
  341. $news = new Newsroom();
  342. $fields = [
  343. 'title' => 'string',
  344. 'summary' => 'string',
  345. 'body' => 'string',
  346. 'category' => 'string',
  347. 'show_timeline' => 'boolean',
  348. 'auth_only' => 'boolean',
  349. 'show_link' => 'boolean',
  350. 'force_modal' => 'boolean',
  351. 'published' => 'published'
  352. ];
  353. foreach($fields as $field => $type) {
  354. switch ($type) {
  355. case 'string':
  356. if($request->{$field} != $news->{$field}) {
  357. if($field == 'title') {
  358. $news->slug = str_slug($request->{$field});
  359. }
  360. $news->{$field} = $request->{$field};
  361. $changed = true;
  362. array_push($changedFields, $field);
  363. }
  364. break;
  365. case 'boolean':
  366. $state = $request->{$field} == 'on' ? true : false;
  367. if($state != $news->{$field}) {
  368. $news->{$field} = $state;
  369. $changed = true;
  370. array_push($changedFields, $field);
  371. }
  372. break;
  373. case 'published':
  374. $state = $request->{$field} == 'on' ? true : false;
  375. $published = $news->published_at != null;
  376. if($state != $published) {
  377. $news->published_at = $state ? now() : null;
  378. $changed = true;
  379. array_push($changedFields, $field);
  380. }
  381. break;
  382. }
  383. }
  384. if($changed) {
  385. $news->save();
  386. }
  387. $redirect = $news->published_at ? $news->permalink() : $news->editUrl();
  388. return redirect($redirect);
  389. }
  390. public function diagnosticsHome(Request $request)
  391. {
  392. return view('admin.diagnostics.home');
  393. }
  394. public function diagnosticsDecrypt(Request $request)
  395. {
  396. $this->validate($request, [
  397. 'payload' => 'required'
  398. ]);
  399. $key = 'exception_report:';
  400. $decrypted = decrypt($request->input('payload'));
  401. if(!starts_with($decrypted, $key)) {
  402. abort(403, 'Can only decrypt error diagnostics');
  403. }
  404. $res = [
  405. 'decrypted' => substr($decrypted, strlen($key))
  406. ];
  407. return response()->json($res);
  408. }
  409. public function stories(Request $request)
  410. {
  411. $stories = Story::with('profile')->latest()->paginate(10);
  412. $stats = StoryService::adminStats();
  413. return view('admin.stories.home', compact('stories', 'stats'));
  414. }
  415. }