Răsfoiți Sursa

Add manual email verification requests

Daniel Supernault 3 ani în urmă
părinte
comite
bc65938757

+ 59 - 0
app/Http/Controllers/Admin/AdminReportController.php

@@ -4,8 +4,12 @@ namespace App\Http\Controllers\Admin;
 
 use Cache;
 use App\Report;
+use App\User;
 use Carbon\Carbon;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Redis;
+use App\Services\AccountService;
+use App\Services\StatusService;
 
 trait AdminReportController
 {
@@ -33,6 +37,7 @@ trait AdminReportController
         $report = Report::findOrFail($id);
 
         $this->handleReportAction($report, $action);
+        Cache::forget('admin-dash:reports:list-cache');
 
         return response()->json(['msg'=> 'Success']);
     }
@@ -52,17 +57,20 @@ trait AdminReportController
                 $item->is_nsfw = true;
                 $item->save();
                 $report->nsfw = true;
+                StatusService::del($item->id);
                 break;
 
             case 'unlist':
                 $item->visibility = 'unlisted';
                 $item->save();
                 Cache::forget('profiles:private');
+                StatusService::del($item->id);
                 break;
 
             case 'delete':
                 // Todo: fire delete job
                 $report->admin_seen = null;
+                StatusService::del($item->id);
                 break;
 
             case 'shadowban':
@@ -115,4 +123,55 @@ trait AdminReportController
         ];
         return response()->json($res);
     }
+
+    public function reportMailVerifications(Request $request)
+    {
+    	$ids = Redis::smembers('email:manual');
+    	$ignored = Redis::smembers('email:manual-ignored');
+    	$reports = [];
+    	if($ids) {
+			$reports = collect($ids)
+				->filter(function($id) use($ignored) {
+					return !in_array($id, $ignored);
+				})
+				->map(function($id) {
+					$account = AccountService::get($id);
+					$user = User::whereProfileId($id)->first();
+					if(!$user) {
+						return [];
+					}
+					$account['email'] = $user->email;
+					return $account;
+				})
+				->filter(function($res) {
+					return isset($res['id']);
+				})
+				->values();
+    	}
+    	return view('admin.reports.mail_verification', compact('reports', 'ignored'));
+    }
+
+    public function reportMailVerifyIgnore(Request $request)
+    {
+    	$id = $request->input('id');
+    	Redis::sadd('email:manual-ignored', $id);
+    	return redirect('/i/admin/reports');
+    }
+
+    public function reportMailVerifyApprove(Request $request)
+    {
+    	$id = $request->input('id');
+    	$user = User::whereProfileId($id)->firstOrFail();
+    	Redis::srem('email:manual', $id);
+    	Redis::srem('email:manual-ignored', $id);
+    	$user->email_verified_at = now();
+    	$user->save();
+    	return redirect('/i/admin/reports');
+    }
+
+    public function reportMailVerifyClearIgnored(Request $request)
+    {
+    	Redis::del('email:manual-ignored');
+    	return [200];
+    }
 }

+ 58 - 17
app/Http/Controllers/AdminController.php

@@ -17,6 +17,7 @@ use App\{
 use DB, Cache;
 use Carbon\Carbon;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Redis;
 use App\Http\Controllers\Admin\{
 	AdminDiscoverController,
 	AdminInstanceController,
@@ -28,12 +29,13 @@ use App\Http\Controllers\Admin\{
 };
 use Illuminate\Validation\Rule;
 use App\Services\AdminStatsService;
+use App\Services\StatusService;
 use App\Services\StoryService;
 
 class AdminController extends Controller
 {
 	use AdminReportController, 
-	AdminDiscoverController, 
+	AdminDiscoverController,
 	AdminMediaController, 
 	AdminSettingsController, 
 	AdminInstanceController,
@@ -54,9 +56,15 @@ class AdminController extends Controller
 
 	public function statuses(Request $request)
 	{
-		$statuses = Status::orderBy('id', 'desc')->simplePaginate(10);
-
-		return view('admin.statuses.home', compact('statuses'));
+		$statuses = Status::orderBy('id', 'desc')->cursorPaginate(10);
+		$data = $statuses->map(function($status) {
+			return StatusService::get($status->id, false);
+		})
+		->filter(function($s) {
+			return $s;
+		})
+		->toArray();
+		return view('admin.statuses.home', compact('statuses', 'data'));
 	}
 
 	public function showStatus(Request $request, $id)
@@ -69,17 +77,45 @@ class AdminController extends Controller
 	public function reports(Request $request)
 	{
 		$filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
-		$reports = Report::whereHas('status')
-		->whereHas('reportedUser')
-		->whereHas('reporter')
-		->orderBy('created_at','desc')
-		->when($filter, function($q, $filter) {
-			return $filter == 'open' ? 
-			$q->whereNull('admin_seen') :
-			$q->whereNotNull('admin_seen');
-		})
-		->paginate(6);
-		return view('admin.reports.home', compact('reports'));
+		$page = $request->input('page') ?? 1;
+
+		$ai = Cache::remember('admin-dash:reports:ai-count', 3600, function() {
+			return AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count();
+		});
+
+		$spam = Cache::remember('admin-dash:reports:spam-count', 3600, function() {
+			return AccountInterstitial::whereType('post.autospam')->whereNull('appeal_handled_at')->count();
+		});
+
+		$mailVerifications = Redis::scard('email:manual');
+
+		if($filter == 'open' && $page == 1) {
+			$reports = Cache::remember('admin-dash:reports:list-cache', 300, function() use($page, $filter) {
+				return Report::whereHas('status')
+					->whereHas('reportedUser')
+					->whereHas('reporter')
+					->orderBy('created_at','desc')
+					->when($filter, function($q, $filter) {
+						return $filter == 'open' ?
+						$q->whereNull('admin_seen') :
+						$q->whereNotNull('admin_seen');
+					})
+					->paginate(6);
+			});
+		} else {
+			$reports = Report::whereHas('status')
+			->whereHas('reportedUser')
+			->whereHas('reporter')
+			->orderBy('created_at','desc')
+			->when($filter, function($q, $filter) {
+				return $filter == 'open' ?
+				$q->whereNull('admin_seen') :
+				$q->whereNotNull('admin_seen');
+			})
+			->paginate(6);
+		}
+
+		return view('admin.reports.home', compact('reports', 'ai', 'spam', 'mailVerifications'));
 	}
 
 	public function showReport(Request $request, $id)
@@ -143,7 +179,7 @@ class AdminController extends Controller
 
 			Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
 			Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
-
+			Cache::forget('admin-dash:reports:spam-count');
 			return redirect('/i/admin/reports/autospam');
 		}
 
@@ -156,8 +192,11 @@ class AdminController extends Controller
 		$appeal->appeal_handled_at = now();
 		$appeal->save();
 
+		StatusService::del($status->id);
+
 		Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
 		Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
+		Cache::forget('admin-dash:reports:spam-count');
 
 		return redirect('/i/admin/reports/autospam');
 	}
@@ -176,7 +215,7 @@ class AdminController extends Controller
 		if($action == 'dismiss') {
 			$appeal->appeal_handled_at = now();
 			$appeal->save();
-
+			Cache::forget('admin-dash:reports:ai-count');
 			return redirect('/i/admin/reports/appeals');
 		}
 
@@ -201,6 +240,8 @@ class AdminController extends Controller
 
 		$appeal->appeal_handled_at = now();
 		$appeal->save();
+		StatusService::del($status->id);
+		Cache::forget('admin-dash:reports:ai-count');
 
 		return redirect('/i/admin/reports/appeals');
 	}

+ 28 - 8
app/Http/Controllers/InternalApiController.php

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
 use Illuminate\Http\Request;
 use App\{
 	AccountInterstitial,
+	Bookmark,
 	DirectMessage,
 	DiscoverCategory,
 	Hashtag,
@@ -19,6 +20,7 @@ use App\{
 	UserFilter,
 };
 use Auth,Cache;
+use Illuminate\Support\Facades\Redis;
 use Carbon\Carbon;
 use League\Fractal;
 use App\Transformer\Api\{
@@ -345,14 +347,18 @@ class InternalApiController extends Controller
 
 	public function bookmarks(Request $request)
 	{
-		$statuses = Auth::user()->profile
-			->bookmarks()
-			->withCount(['likes','comments'])
-			->orderBy('created_at', 'desc')
-			->simplePaginate(10);
-
-		$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
-		$res = $this->fractal->createData($resource)->toArray();
+		$res = Bookmark::whereProfileId($request->user()->profile_id)
+			->orderByDesc('created_at')
+			->simplePaginate(10)
+			->map(function($bookmark) {
+				$status = StatusService::get($bookmark->status_id);
+				$status['bookmarked_at'] = $bookmark->created_at->format('c');
+				return $status;
+			})
+			->filter(function($bookmark) {
+				return isset($bookmark['id']);
+			})
+			->values();
 
 		return response()->json($res);
 	}
@@ -456,4 +462,18 @@ class InternalApiController extends Controller
 		$template = $status->in_reply_to_id ? 'status.reply' : 'status.remote';
 		return view($template, compact('user', 'status'));
 	}
+
+	public function requestEmailVerification(Request $request)
+	{
+		$pid = $request->user()->profile_id;
+		$exists = Redis::sismember('email:manual', $pid);
+		return view('account.email.request_verification', compact('exists'));
+	}
+
+	public function requestEmailVerificationStore(Request $request)
+	{
+		$pid = $request->user()->profile_id;
+		Redis::sadd('email:manual', $pid);
+		return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']);
+	}
 }

+ 20 - 4
resources/views/account/verify_email.blade.php

@@ -13,19 +13,35 @@
             <p class="font-weight-bold mb-0">{{ session('error') }}</p>
         </div>
     @endif
+
+    @if(Auth::user()->email_verified_at)
+    	<p class="lead text-center mt-5">Your email is already verified. <a href="/" class="font-weight-bold">Click here</a> to go home.</p>
+    @else
     <div class="card shadow-none border">
       <div class="card-header font-weight-bold bg-white">Confirm Email Address</div>
       <div class="card-body">
-        <p class="lead">You need to confirm your email address (<span class="font-weight-bold">{{Auth::user()->email}}</span>) before you can proceed.</p>
-    	<p class="lead">You can change your email address <a href="/settings/email">here</a>.</p>
-    	<p class="small">If you don't recieve an email within 30 minutes, you can <a href="/site/contact">contact the administrator</a>.</p>
-        <hr>
+        <p class="lead text-break">You need to confirm your email address <span class="font-weight-bold">{{Auth::user()->email}}</span> before you can proceed.</p>
+        @if(!$recentSent)
         <form method="post">
           @csrf
           <button type="submit" class="btn btn-primary btn-block py-1 font-weight-bold">Send Confirmation Email</button>
         </form>
+        @else
+        	<button class="btn btn-primary btn-block py-1 font-weight-bold" disabled>Confirmation Email Sent</button>
+        @endif
+    	<p class="mt-3 mb-0 small text-muted"><a href="/settings/email" class="font-weight-bold">Click here</a> to change your email address.</p>
       </div>
     </div>
+
+    @if($recentSent)
+    <div class="card mt-3 border shadow-none">
+    	<div class="card-body">
+    		<p class="mb-0 text-muted">If you are experiencing issues receiving your email confirmation, you can <a href="/i/verify-email/request" class="font-weight-bold">request manual verification</a>.</p>
+    	</div>
+    </div>
+    @endif
+
+    @endif
   </div>
 </div>
 @endsection

+ 6 - 3
resources/views/admin/reports/home.blade.php

@@ -15,11 +15,14 @@
 			@endif
 		</div>
 	</div>
-	@php($ai = App\AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count())
-	@php($spam = App\AccountInterstitial::whereType('post.autospam')->whereNull('appeal_handled_at')->count())
-	@if($ai || $spam)
+
+	@if($ai || $spam || $mailVerifications)
 	<div class="col-12 col-md-8 offset-md-2">
 		<div class="mb-4">
+			<a class="btn btn-outline-primary px-5 py-3 mr-3" href="/i/admin/reports/email-verifications">
+				<p class="font-weight-bold h4 mb-0">{{$mailVerifications}}</p>
+				Email Verify {{$mailVerifications == 1 ? 'Request' : 'Requests'}}
+			</a>
 			<a class="btn btn-outline-primary px-5 py-3 mr-3" href="/i/admin/reports/appeals">
 				<p class="font-weight-bold h4 mb-0">{{$ai}}</p>
 				Appeal {{$ai == 1 ? 'Request' : 'Requests'}}

+ 75 - 0
resources/views/admin/reports/mail_verification.blade.php

@@ -0,0 +1,75 @@
+@extends('admin.partial.template-full')
+
+@section('section')
+	<div class="title mb-3 d-flex justify-content-between align-items-center">
+		<div>
+			<h3 class="font-weight-bold d-inline-block">Email Verification Requests</h3>
+			@if($ignored)
+				<p>
+					You are ignoring <strong>{{ count($ignored) }}</strong> mail verification requests. <a href="#" class="font-weight-bold clear-ignored">Clear ignored requests</a>
+				</p>
+			@endif
+		</div>
+		<div class="float-right">
+		</div>
+	</div>
+	<div class="col-12 col-md-8 offset-md-2">
+		<div class="card shadow-none border">
+			<div class="list-group list-group-flush">
+				@foreach($reports as $report)
+				<div class="list-group-item">
+					<div class="media align-items-center">
+						<img src="{{ $report['avatar'] }}" width="50" height="50" class="rounded-circle border mr-3">
+						<div class="media-body">
+							<p class="font-weight-bold mb-0">{{ $report['username'] }}</p>
+							<p class="text-muted mb-0">{{ $report['email'] }}</p>
+						</div>
+						<div>
+							<button class="action-btn btn btn-light font-weight-bold mr-2" data-action="ignore" data-id="{{$report['id']}}">Ignore</button>
+							<button class="action-btn btn btn-primary font-weight-bold" data-action="approve" data-id="{{$report['id']}}"><i class="far fa-check-circle fa-lg mr-2"></i>Approve</button>
+						</div>
+					</div>
+				</div>
+				@endforeach
+
+				@if(count($reports) == 0)
+				<div class="list-group-item">
+					<p class="font-weight-bold mb-0">No email verification requests found!</p>
+				</div>
+				@endif
+			</div>
+		</div>
+	</div>
+@endsection
+
+@push('scripts')
+<script type="text/javascript">
+	$('.clear-ignored').click((e) => {
+		e.preventDefault();
+		if(!window.confirm('Are you sure you want to clear all ignored requests?')) {
+			return;
+		}
+		axios.post('/i/admin/reports/email-verifications/clear-ignored')
+		.then(res => {
+			location.reload();
+		});
+	});
+
+	$('.action-btn').click((e) => {
+		e.preventDefault();
+		let type = e.currentTarget.getAttribute('data-action');
+		let id = e.currentTarget.getAttribute('data-id');
+		if(!window.confirm(`Are you sure you want to ${type} this email verification request?`)) {
+			return;
+		}
+		axios.post('/i/admin/reports/email-verifications/' + type, {
+			id: id
+		}).then(res => {
+			location.href = '/i/admin/reports';
+		}).catch(err => {
+			swal('Oops!', 'An error occured', 'error');
+			console.log(err);
+		})
+	});
+</script>
+@endpush

+ 6 - 0
routes/web.php

@@ -14,6 +14,10 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
 	Route::get('reports/appeals', 'AdminController@appeals');
 	Route::get('reports/appeal/{id}', 'AdminController@showAppeal');
 	Route::post('reports/appeal/{id}', 'AdminController@updateAppeal');
+	Route::get('reports/email-verifications', 'AdminController@reportMailVerifications');
+	Route::post('reports/email-verifications/ignore', 'AdminController@reportMailVerifyIgnore');
+	Route::post('reports/email-verifications/approve', 'AdminController@reportMailVerifyApprove');
+	Route::post('reports/email-verifications/clear-ignored', 'AdminController@reportMailVerifyClearIgnored');
 	Route::redirect('stories', '/stories/list');
 	Route::get('stories/list', 'AdminController@stories')->name('admin.stories');
 	Route::redirect('statuses', '/statuses/list');
@@ -273,6 +277,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
 
 		Route::get('verify-email', 'AccountController@verifyEmail');
 		Route::post('verify-email', 'AccountController@sendVerifyEmail');
+		Route::get('verify-email/request', 'InternalApiController@requestEmailVerification');
+		Route::post('verify-email/request', 'InternalApiController@requestEmailVerificationStore');
 		Route::get('confirm-email/{userToken}/{randomToken}', 'AccountController@confirmVerifyEmail');
 
 		Route::get('auth/sudo', 'AccountController@sudoMode');