Merge pull request #1491 from pixelfed/frontend-ui-refactor

Update AccountController
This commit is contained in:
daniel 2019-07-10 23:10:31 -06:00 committed by GitHub
commit 0b7a782b52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 433 additions and 512 deletions

View File

@ -2,476 +2,441 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\EmailVerification;
use App\Follower;
use App\FollowRequest;
use App\Jobs\FollowPipeline\FollowPipeline;
use App\Mail\ConfirmEmail;
use App\Notification;
use App\Profile;
use App\User;
use App\UserFilter;
use Auth;
use Cache;
use Carbon\Carbon; use Carbon\Carbon;
use App\Mail\ConfirmEmail;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Mail; use {Auth, Cache, Mail, Redis};
use Redis;
use PragmaRX\Google2FA\Google2FA; use PragmaRX\Google2FA\Google2FA;
use App\Jobs\FollowPipeline\FollowPipeline;
use App\{
EmailVerification,
Follower,
FollowRequest,
Notification,
Profile,
User,
UserFilter
};
class AccountController extends Controller class AccountController extends Controller
{ {
protected $filters = [ protected $filters = [
'user.mute', 'user.mute',
'user.block', 'user.block',
]; ];
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function notifications(Request $request) public function notifications(Request $request)
{ {
return view('account.activity'); return view('account.activity');
} }
public function followingActivity(Request $request) public function followingActivity(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'page' => 'nullable|min:1|max:3', 'page' => 'nullable|min:1|max:3',
'a' => 'nullable|alpha_dash', 'a' => 'nullable|alpha_dash',
]); ]);
$profile = Auth::user()->profile;
$action = $request->input('a'); $action = $request->input('a');
$allowed = ['like', 'follow']; $allowed = ['like', 'follow'];
$timeago = Carbon::now()->subMonths(3); $timeago = Carbon::now()->subMonths(3);
$following = $profile->following->pluck('id');
$notifications = Notification::whereIn('actor_id', $following) $profile = Auth::user()->profile;
->whereIn('action', $allowed) $following = $profile->following->pluck('id');
->where('actor_id', '<>', $profile->id)
->where('profile_id', '<>', $profile->id) $notifications = Notification::whereIn('actor_id', $following)
->whereDate('created_at', '>', $timeago) ->whereIn('action', $allowed)
->orderBy('notifications.created_at', 'desc') ->where('actor_id', '<>', $profile->id)
->simplePaginate(30); ->where('profile_id', '<>', $profile->id)
->whereDate('created_at', '>', $timeago)
return view('account.following', compact('profile', 'notifications')); ->orderBy('notifications.created_at', 'desc')
} ->simplePaginate(30);
public function verifyEmail(Request $request) return view('account.following', compact('profile', 'notifications'));
{ }
return view('account.verify_email');
} public function verifyEmail(Request $request)
{
public function sendVerifyEmail(Request $request) return view('account.verify_email');
{ }
$recentAttempt = EmailVerification::whereUserId(Auth::id())
->whereDate('created_at', '>', now()->subHours(12))->count(); public function sendVerifyEmail(Request $request)
{
if ($recentAttempt > 0) { $recentAttempt = EmailVerification::whereUserId(Auth::id())
return redirect()->back()->with('error', 'A verification email has already been sent recently. Please check your email, or try again later.'); ->whereDate('created_at', '>', now()->subHours(12))->count();
}
if ($recentAttempt > 0) {
EmailVerification::whereUserId(Auth::id())->delete(); return redirect()->back()->with('error', 'A verification email has already been sent recently. Please check your email, or try again later.');
}
$user = User::whereNull('email_verified_at')->find(Auth::id());
$utoken = str_random(40); EmailVerification::whereUserId(Auth::id())->delete();
$rtoken = str_random(128);
$user = User::whereNull('email_verified_at')->find(Auth::id());
$verify = new EmailVerification(); $utoken = str_random(64);
$verify->user_id = $user->id; $rtoken = str_random(128);
$verify->email = $user->email;
$verify->user_token = $utoken; $verify = new EmailVerification();
$verify->random_token = $rtoken; $verify->user_id = $user->id;
$verify->save(); $verify->email = $user->email;
$verify->user_token = $utoken;
Mail::to($user->email)->send(new ConfirmEmail($verify)); $verify->random_token = $rtoken;
$verify->save();
return redirect()->back()->with('status', 'Verification email sent!');
} Mail::to($user->email)->send(new ConfirmEmail($verify));
public function confirmVerifyEmail(Request $request, $userToken, $randomToken) return redirect()->back()->with('status', 'Verification email sent!');
{ }
$verify = EmailVerification::where('user_token', $userToken)
->where('random_token', $randomToken) public function confirmVerifyEmail(Request $request, $userToken, $randomToken)
->firstOrFail(); {
$verify = EmailVerification::where('user_token', $userToken)
if (Auth::id() === $verify->user_id && ->where('created_at', '>', now()->subWeeks(2))
$verify->user_token === $userToken && ->where('random_token', $randomToken)
$verify->random_token === $randomToken) { ->firstOrFail();
$user = User::find(Auth::id());
$user->email_verified_at = Carbon::now(); if (Auth::id() === $verify->user_id && $verify->user_token === $userToken && $verify->random_token === $randomToken) {
$user->save(); $user = User::find(Auth::id());
$user->email_verified_at = Carbon::now();
return redirect('/'); $user->save();
} else {
abort(403); return redirect('/');
} } else {
} abort(403);
}
public function fetchNotifications(int $id) }
{
$key = config('cache.prefix').":user.{$id}.notifications"; public function messages()
$redis = Redis::connection(); {
$notifications = $redis->lrange($key, 0, 30); return view('account.messages');
if (empty($notifications)) { }
$notifications = Notification::whereProfileId($id)
->orderBy('id', 'desc')->take(30)->get(); public function direct()
} else { {
$notifications = $this->hydrateNotifications($notifications); return view('account.direct');
} }
return $notifications; public function showMessage(Request $request, $id)
} {
return view('account.message');
public function hydrateNotifications($keys) }
{
$prefix = 'notification.'; public function mute(Request $request)
$notifications = collect([]); {
foreach ($keys as $key) { $this->validate($request, [
$notifications->push(Cache::get("{$prefix}{$key}")); 'type' => 'required|alpha_dash',
} 'item' => 'required|integer|min:1',
]);
return $notifications;
} $user = Auth::user()->profile;
$type = $request->input('type');
public function messages() $item = $request->input('item');
{ $action = $type . '.mute';
return view('account.messages');
} if (!in_array($action, $this->filters)) {
return abort(406);
public function direct() }
{ $filterable = [];
return view('account.direct'); switch ($type) {
} case 'user':
$profile = Profile::findOrFail($item);
public function showMessage(Request $request, $id) if ($profile->id == $user->id) {
{ return abort(403);
return view('account.message'); }
} $class = get_class($profile);
$filterable['id'] = $profile->id;
public function mute(Request $request) $filterable['type'] = $class;
{ break;
$this->validate($request, [ }
'type' => 'required|alpha_dash',
'item' => 'required|integer|min:1', $filter = UserFilter::firstOrCreate([
]); 'user_id' => $user->id,
'filterable_id' => $filterable['id'],
$user = Auth::user()->profile; 'filterable_type' => $filterable['type'],
$type = $request->input('type'); 'filter_type' => 'mute',
$item = $request->input('item'); ]);
$action = $type . '.mute';
$pid = $user->id;
if (!in_array($action, $this->filters)) { Cache::forget("user:filter:list:$pid");
return abort(406); Cache::forget("feature:discover:posts:$pid");
} Cache::forget("api:local:exp:rec:$pid");
$filterable = [];
switch ($type) { return redirect()->back();
case 'user': }
$profile = Profile::findOrFail($item);
if ($profile->id == $user->id) { public function unmute(Request $request)
return abort(403); {
} $this->validate($request, [
$class = get_class($profile); 'type' => 'required|alpha_dash',
$filterable['id'] = $profile->id; 'item' => 'required|integer|min:1',
$filterable['type'] = $class; ]);
break;
$user = Auth::user()->profile;
default: $type = $request->input('type');
// code... $item = $request->input('item');
break; $action = $type . '.mute';
}
if (!in_array($action, $this->filters)) {
$filter = UserFilter::firstOrCreate([ return abort(406);
'user_id' => $user->id, }
'filterable_id' => $filterable['id'], $filterable = [];
'filterable_type' => $filterable['type'], switch ($type) {
'filter_type' => 'mute', case 'user':
]); $profile = Profile::findOrFail($item);
if ($profile->id == $user->id) {
$pid = $user->id; return abort(403);
Cache::forget("user:filter:list:$pid"); }
Cache::forget("feature:discover:posts:$pid"); $class = get_class($profile);
Cache::forget("api:local:exp:rec:$pid"); $filterable['id'] = $profile->id;
$filterable['type'] = $class;
return redirect()->back(); break;
}
default:
public function unmute(Request $request) abort(400);
{ break;
$this->validate($request, [ }
'type' => 'required|alpha_dash',
'item' => 'required|integer|min:1', $filter = UserFilter::whereUserId($user->id)
]); ->whereFilterableId($filterable['id'])
->whereFilterableType($filterable['type'])
$user = Auth::user()->profile; ->whereFilterType('mute')
$type = $request->input('type'); ->first();
$item = $request->input('item');
$action = $type . '.mute'; if($filter) {
$filter->delete();
if (!in_array($action, $this->filters)) { }
return abort(406);
} $pid = $user->id;
$filterable = []; Cache::forget("user:filter:list:$pid");
switch ($type) { Cache::forget("feature:discover:posts:$pid");
case 'user': Cache::forget("api:local:exp:rec:$pid");
$profile = Profile::findOrFail($item);
if ($profile->id == $user->id) { if($request->wantsJson()) {
return abort(403); return response()->json([200]);
} } else {
$class = get_class($profile); return redirect()->back();
$filterable['id'] = $profile->id; }
$filterable['type'] = $class; }
break;
public function block(Request $request)
default: {
abort(400); $this->validate($request, [
break; 'type' => 'required|alpha_dash',
} 'item' => 'required|integer|min:1',
]);
$filter = UserFilter::whereUserId($user->id)
->whereFilterableId($filterable['id']) $user = Auth::user()->profile;
->whereFilterableType($filterable['type']) $type = $request->input('type');
->whereFilterType('mute') $item = $request->input('item');
->first(); $action = $type.'.block';
if (!in_array($action, $this->filters)) {
if($filter) { return abort(406);
$filter->delete(); }
} $filterable = [];
switch ($type) {
$pid = $user->id; case 'user':
Cache::forget("user:filter:list:$pid"); $profile = Profile::findOrFail($item);
Cache::forget("feature:discover:posts:$pid"); if ($profile->id == $user->id) {
Cache::forget("api:local:exp:rec:$pid"); return abort(403);
}
if($request->wantsJson()) { $class = get_class($profile);
return response()->json([200]); $filterable['id'] = $profile->id;
} else { $filterable['type'] = $class;
return redirect()->back();
} Follower::whereProfileId($profile->id)->whereFollowingId($user->id)->delete();
} Notification::whereProfileId($user->id)->whereActorId($profile->id)->delete();
break;
public function block(Request $request) }
{
$this->validate($request, [ $filter = UserFilter::firstOrCreate([
'type' => 'required|alpha_dash', 'user_id' => $user->id,
'item' => 'required|integer|min:1', 'filterable_id' => $filterable['id'],
]); 'filterable_type' => $filterable['type'],
'filter_type' => 'block',
$user = Auth::user()->profile; ]);
$type = $request->input('type');
$item = $request->input('item'); $pid = $user->id;
$action = $type.'.block'; Cache::forget("user:filter:list:$pid");
if (!in_array($action, $this->filters)) { Cache::forget("feature:discover:posts:$pid");
return abort(406); Cache::forget("api:local:exp:rec:$pid");
}
$filterable = []; return redirect()->back();
switch ($type) { }
case 'user':
$profile = Profile::findOrFail($item);
if ($profile->id == $user->id) { public function unblock(Request $request)
return abort(403); {
} $this->validate($request, [
$class = get_class($profile); 'type' => 'required|alpha_dash',
$filterable['id'] = $profile->id; 'item' => 'required|integer|min:1',
$filterable['type'] = $class; ]);
Follower::whereProfileId($profile->id)->whereFollowingId($user->id)->delete(); $user = Auth::user()->profile;
Notification::whereProfileId($user->id)->whereActorId($profile->id)->delete(); $type = $request->input('type');
break; $item = $request->input('item');
$action = $type . '.block';
default: if (!in_array($action, $this->filters)) {
// code... return abort(406);
break; }
} $filterable = [];
switch ($type) {
$filter = UserFilter::firstOrCreate([ case 'user':
'user_id' => $user->id, $profile = Profile::findOrFail($item);
'filterable_id' => $filterable['id'], if ($profile->id == $user->id) {
'filterable_type' => $filterable['type'], return abort(403);
'filter_type' => 'block', }
]); $class = get_class($profile);
$filterable['id'] = $profile->id;
$pid = $user->id; $filterable['type'] = $class;
Cache::forget("user:filter:list:$pid"); break;
Cache::forget("feature:discover:posts:$pid");
Cache::forget("api:local:exp:rec:$pid"); default:
abort(400);
return redirect()->back(); break;
} }
public function unblock(Request $request) $filter = UserFilter::whereUserId($user->id)
{ ->whereFilterableId($filterable['id'])
$this->validate($request, [ ->whereFilterableType($filterable['type'])
'type' => 'required|alpha_dash', ->whereFilterType('block')
'item' => 'required|integer|min:1', ->first();
]);
if($filter) {
$user = Auth::user()->profile; $filter->delete();
$type = $request->input('type'); }
$item = $request->input('item');
$action = $type . '.block'; $pid = $user->id;
if (!in_array($action, $this->filters)) { Cache::forget("user:filter:list:$pid");
return abort(406); Cache::forget("feature:discover:posts:$pid");
} Cache::forget("api:local:exp:rec:$pid");
$filterable = [];
switch ($type) { return redirect()->back();
case 'user': }
$profile = Profile::findOrFail($item);
if ($profile->id == $user->id) { public function followRequests(Request $request)
return abort(403); {
} $pid = Auth::user()->profile->id;
$class = get_class($profile); $followers = FollowRequest::whereFollowingId($pid)->orderBy('id','desc')->whereIsRejected(0)->simplePaginate(10);
$filterable['id'] = $profile->id; return view('account.follow-requests', compact('followers'));
$filterable['type'] = $class; }
break;
public function followRequestHandle(Request $request)
default: {
abort(400); $this->validate($request, [
break; 'action' => 'required|string|max:10',
} 'id' => 'required|integer|min:1'
]);
$filter = UserFilter::whereUserId($user->id) $pid = Auth::user()->profile->id;
->whereFilterableId($filterable['id']) $action = $request->input('action') === 'accept' ? 'accept' : 'reject';
->whereFilterableType($filterable['type']) $id = $request->input('id');
->whereFilterType('block') $followRequest = FollowRequest::whereFollowingId($pid)->findOrFail($id);
->first(); $follower = $followRequest->follower;
if($filter) { switch ($action) {
$filter->delete(); case 'accept':
} $follow = new Follower();
$follow->profile_id = $follower->id;
$pid = $user->id; $follow->following_id = $pid;
Cache::forget("user:filter:list:$pid"); $follow->save();
Cache::forget("feature:discover:posts:$pid"); FollowPipeline::dispatch($follow);
Cache::forget("api:local:exp:rec:$pid"); $followRequest->delete();
break;
return redirect()->back();
} case 'reject':
$followRequest->is_rejected = true;
public function followRequests(Request $request) $followRequest->save();
{ break;
$pid = Auth::user()->profile->id; }
$followers = FollowRequest::whereFollowingId($pid)->orderBy('id','desc')->whereIsRejected(0)->simplePaginate(10);
return view('account.follow-requests', compact('followers')); return response()->json(['msg' => 'success'], 200);
} }
public function followRequestHandle(Request $request) public function sudoMode(Request $request)
{ {
$this->validate($request, [ return view('auth.sudo');
'action' => 'required|string|max:10', }
'id' => 'required|integer|min:1'
]); public function sudoModeVerify(Request $request)
{
$pid = Auth::user()->profile->id; $this->validate($request, [
$action = $request->input('action') === 'accept' ? 'accept' : 'reject'; 'password' => 'required|string|max:500'
$id = $request->input('id'); ]);
$followRequest = FollowRequest::whereFollowingId($pid)->findOrFail($id); $user = Auth::user();
$follower = $followRequest->follower; $password = $request->input('password');
$next = $request->session()->get('redirectNext', '/');
switch ($action) { if(password_verify($password, $user->password) === true) {
case 'accept': $request->session()->put('sudoMode', time());
$follow = new Follower(); return redirect($next);
$follow->profile_id = $follower->id; } else {
$follow->following_id = $pid; return redirect()
$follow->save(); ->back()
FollowPipeline::dispatch($follow); ->withErrors(['password' => __('auth.failed')]);
$followRequest->delete(); }
break; }
case 'reject': public function twoFactorCheckpoint(Request $request)
$followRequest->is_rejected = true; {
$followRequest->save(); return view('auth.checkpoint');
break; }
}
public function twoFactorVerify(Request $request)
return response()->json(['msg' => 'success'], 200); {
} $this->validate($request, [
'code' => 'required|string|max:32'
public function sudoMode(Request $request) ]);
{ $user = Auth::user();
return view('auth.sudo'); $code = $request->input('code');
} $google2fa = new Google2FA();
$verify = $google2fa->verifyKey($user->{'2fa_secret'}, $code);
public function sudoModeVerify(Request $request) if($verify) {
{ $request->session()->push('2fa.session.active', true);
$this->validate($request, [ return redirect('/');
'password' => 'required|string|max:500' } else {
]);
$user = Auth::user(); if($this->twoFactorBackupCheck($request, $code, $user)) {
$password = $request->input('password'); return redirect('/');
$next = $request->session()->get('redirectNext', '/'); }
if(password_verify($password, $user->password) === true) {
$request->session()->put('sudoMode', time()); if($request->session()->has('2fa.attempts')) {
return redirect($next); $count = (int) $request->session()->has('2fa.attempts');
} else { $request->session()->push('2fa.attempts', $count + 1);
return redirect() } else {
->back() $request->session()->push('2fa.attempts', 1);
->withErrors(['password' => __('auth.failed')]); }
} return redirect()->back()->withErrors([
} 'code' => 'Invalid code'
]);
public function twoFactorCheckpoint(Request $request) }
{ }
return view('auth.checkpoint');
} protected function twoFactorBackupCheck($request, $code, User $user)
{
public function twoFactorVerify(Request $request) $backupCodes = $user->{'2fa_backup_codes'};
{ if($backupCodes) {
$this->validate($request, [ $codes = json_decode($backupCodes, true);
'code' => 'required|string|max:32' foreach ($codes as $c) {
]); if(hash_equals($c, $code)) {
$user = Auth::user(); $codes = array_flatten(array_diff($codes, [$code]));
$code = $request->input('code'); $user->{'2fa_backup_codes'} = json_encode($codes);
$google2fa = new Google2FA(); $user->save();
$verify = $google2fa->verifyKey($user->{'2fa_secret'}, $code); $request->session()->push('2fa.session.active', true);
if($verify) { return true;
$request->session()->push('2fa.session.active', true); } else {
return redirect('/'); return false;
} else { }
}
if($this->twoFactorBackupCheck($request, $code, $user)) { } else {
return redirect('/'); return false;
} }
}
if($request->session()->has('2fa.attempts')) {
$count = (int) $request->session()->has('2fa.attempts'); public function accountRestored(Request $request)
$request->session()->push('2fa.attempts', $count + 1); {
} else { }
$request->session()->push('2fa.attempts', 1);
}
return redirect()->back()->withErrors([
'code' => 'Invalid code'
]);
}
}
protected function twoFactorBackupCheck($request, $code, User $user)
{
$backupCodes = $user->{'2fa_backup_codes'};
if($backupCodes) {
$codes = json_decode($backupCodes, true);
foreach ($codes as $c) {
if(hash_equals($c, $code)) {
// remove code
$codes = array_flatten(array_diff($codes, [$code]));
$user->{'2fa_backup_codes'} = json_encode($codes);
$user->save();
$request->session()->push('2fa.session.active', true);
return true;
} else {
return false;
}
}
} else {
return false;
}
}
public function accountRestored(Request $request)
{
//
}
} }

View File

@ -50,38 +50,7 @@ class InternalApiController extends Controller
// deprecated // deprecated
public function discover(Request $request) public function discover(Request $request)
{ {
$profile = Auth::user()->profile; return;
$pid = $profile->id;
$following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(60), function() use ($pid) {
return Follower::whereProfileId($pid)->pluck('following_id')->toArray();
});
$filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(60), function() use($pid) {
return UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id')->toArray();
});
$following = array_merge($following, $filters);
$posts = Status::select('id', 'caption', 'profile_id')
->whereHas('media')
->whereIsNsfw(false)
->whereVisibility('public')
->whereNotIn('profile_id', $following)
->with('media')
->orderBy('created_at', 'desc')
->take(21)
->get();
$res = [
'posts' => $posts->map(function($post) {
return [
'url' => $post->url(),
'thumb' => $post->thumb(),
];
})
];
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
} }
public function discoverPosts(Request $request) public function discoverPosts(Request $request)
@ -155,22 +124,9 @@ class InternalApiController extends Controller
return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT);
} }
public function notificationMarkAllRead(Request $request)
{
$profile = Auth::user()->profile;
$notifications = Notification::whereProfileId($profile->id)->get();
foreach($notifications as $n) {
$n->read_at = Carbon::now();
$n->save();
}
return;
}
public function statusReplies(Request $request, int $id) public function statusReplies(Request $request, int $id)
{ {
$parent = Status::findOrFail($id); $parent = Status::whereScope('public')->findOrFail($id);
$children = Status::whereInReplyToId($parent->id) $children = Status::whereInReplyToId($parent->id)
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')