mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-22 21:13:16 +00:00
Merge pull request #334 from dansup/frontend-ui-refactor
Frontend ui refactor
This commit is contained in:
commit
56750833e9
49 changed files with 2895 additions and 312 deletions
10
app/AccountLog.php
Normal file
10
app/AccountLog.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class AccountLog extends Model
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
36
app/Events/AuthLoginEvent.php
Normal file
36
app/Events/AuthLoginEvent.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use App\{User, UserSetting};
|
||||||
|
|
||||||
|
class AuthLoginEvent
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(User $user)
|
||||||
|
{
|
||||||
|
if(empty($user->settings)) {
|
||||||
|
$settings = new UserSetting;
|
||||||
|
$settings->user_id = $user->id;
|
||||||
|
$settings->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class Hashtag extends Model
|
class Hashtag extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = ['name','slug'];
|
public $fillable = ['name','slug'];
|
||||||
|
|
||||||
public function posts()
|
public function posts()
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,15 +18,24 @@ class AccountController extends Controller
|
||||||
public function notifications(Request $request)
|
public function notifications(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'page' => 'nullable|min:1|max:3'
|
'page' => 'nullable|min:1|max:3',
|
||||||
|
'a' => 'nullable|alpha_dash',
|
||||||
]);
|
]);
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
|
$action = $request->input('a');
|
||||||
$timeago = Carbon::now()->subMonths(6);
|
$timeago = Carbon::now()->subMonths(6);
|
||||||
|
if($action && in_array($action, ['comment', 'follow', 'mention'])) {
|
||||||
|
$notifications = Notification::whereProfileId($profile->id)
|
||||||
|
->whereAction($action)
|
||||||
|
->whereDate('created_at', '>', $timeago)
|
||||||
|
->orderBy('id','desc')
|
||||||
|
->simplePaginate(30);
|
||||||
|
} else {
|
||||||
$notifications = Notification::whereProfileId($profile->id)
|
$notifications = Notification::whereProfileId($profile->id)
|
||||||
->whereDate('created_at', '>', $timeago)
|
->whereDate('created_at', '>', $timeago)
|
||||||
->orderBy('id','desc')
|
->orderBy('id','desc')
|
||||||
->take(30)
|
->simplePaginate(30);
|
||||||
->simplePaginate();
|
}
|
||||||
|
|
||||||
return view('account.activity', compact('profile', 'notifications'));
|
return view('account.activity', compact('profile', 'notifications'));
|
||||||
}
|
}
|
||||||
|
@ -38,10 +47,19 @@ class AccountController extends Controller
|
||||||
|
|
||||||
public function sendVerifyEmail(Request $request)
|
public function sendVerifyEmail(Request $request)
|
||||||
{
|
{
|
||||||
if(EmailVerification::whereUserId(Auth::id())->count() !== 0) {
|
$timeLimit = Carbon::now()->subDays(1)->toDateTimeString();
|
||||||
return redirect()->back()->with('status', 'A verification email has already been sent! Please check your email.');
|
$recentAttempt = EmailVerification::whereUserId(Auth::id())
|
||||||
|
->where('created_at', '>', $timeLimit)->count();
|
||||||
|
$exists = EmailVerification::whereUserId(Auth::id())->count();
|
||||||
|
|
||||||
|
if($recentAttempt == 1 && $exists == 1) {
|
||||||
|
return redirect()->back()->with('error', 'A verification email has already been sent recently. Please check your email, or try again later.');
|
||||||
|
} elseif ($recentAttempt == 0 && $exists !== 0) {
|
||||||
|
// Delete old verification and send new one.
|
||||||
|
EmailVerification::whereUserId(Auth::id())->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$user = User::whereNull('email_verified_at')->find(Auth::id());
|
$user = User::whereNull('email_verified_at')->find(Auth::id());
|
||||||
$utoken = hash('sha512', $user->id);
|
$utoken = hash('sha512', $user->id);
|
||||||
$rtoken = str_random(40);
|
$rtoken = str_random(40);
|
||||||
|
@ -60,14 +78,15 @@ class AccountController extends Controller
|
||||||
|
|
||||||
public function confirmVerifyEmail(Request $request, $userToken, $randomToken)
|
public function confirmVerifyEmail(Request $request, $userToken, $randomToken)
|
||||||
{
|
{
|
||||||
$verify = EmailVerification::where(DB::raw('BINARY user_token'), $userToken)
|
$verify = EmailVerification::where('user_token', $userToken)
|
||||||
->where(DB::raw('BINARY random_token'), $randomToken)
|
->where('random_token', $randomToken)
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
if(Auth::id() === $verify->user_id) {
|
if(Auth::id() === $verify->user_id) {
|
||||||
$user = User::find(Auth::id());
|
$user = User::find(Auth::id());
|
||||||
$user->email_verified_at = Carbon::now();
|
$user->email_verified_at = Carbon::now();
|
||||||
$user->save();
|
$user->save();
|
||||||
return redirect('/timeline');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,4 +114,5 @@ class AccountController extends Controller
|
||||||
}
|
}
|
||||||
return $notifications;
|
return $notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\{AccountLog, User};
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ class LoginController extends Controller
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $redirectTo = '/home';
|
protected $redirectTo = '/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new controller instance.
|
* Create a new controller instance.
|
||||||
|
@ -56,4 +57,25 @@ class LoginController extends Controller
|
||||||
|
|
||||||
$this->validate($request, $rules);
|
$this->validate($request, $rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user has been authenticated.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param mixed $user
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function authenticated($request, $user)
|
||||||
|
{
|
||||||
|
$log = new AccountLog;
|
||||||
|
$log->user_id = $user->id;
|
||||||
|
$log->item_id = $user->id;
|
||||||
|
$log->item_type = 'App\User';
|
||||||
|
$log->action = 'auth.login';
|
||||||
|
$log->message = 'Account Login';
|
||||||
|
$log->link = null;
|
||||||
|
$log->ip_address = $request->ip();
|
||||||
|
$log->user_agent = $request->userAgent();
|
||||||
|
$log->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class ImportDataController extends Controller
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Auth, Hashids;
|
use Auth, Cache, Hashids;
|
||||||
use App\{Like, Profile, Status, User};
|
use App\{Like, Profile, Status, User};
|
||||||
use App\Jobs\LikePipeline\LikePipeline;
|
use App\Jobs\LikePipeline\LikePipeline;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class LikeController extends Controller
|
||||||
|
|
||||||
if($status->likes()->whereProfileId($profile->id)->count() !== 0) {
|
if($status->likes()->whereProfileId($profile->id)->count() !== 0) {
|
||||||
$like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail();
|
$like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail();
|
||||||
$like->delete();
|
$like->forceDelete();
|
||||||
$count--;
|
$count--;
|
||||||
} else {
|
} else {
|
||||||
$like = new Like;
|
$like = new Like;
|
||||||
|
@ -35,9 +35,15 @@ class LikeController extends Controller
|
||||||
$like->status_id = $status->id;
|
$like->status_id = $status->id;
|
||||||
$like->save();
|
$like->save();
|
||||||
$count++;
|
$count++;
|
||||||
|
LikePipeline::dispatch($like);
|
||||||
}
|
}
|
||||||
|
|
||||||
LikePipeline::dispatch($like);
|
$likes = Like::whereProfileId($profile->id)
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->take(1000)
|
||||||
|
->pluck('status_id');
|
||||||
|
|
||||||
|
Cache::put('api:like-ids:user:'.$profile->id, $likes, 1440);
|
||||||
|
|
||||||
if($request->ajax()) {
|
if($request->ajax()) {
|
||||||
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
|
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
|
||||||
|
|
|
@ -21,7 +21,6 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
$mimes = [
|
$mimes = [
|
||||||
'application/activity+json',
|
'application/activity+json',
|
||||||
'application/ld+json',
|
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ class ProfileController extends Controller
|
||||||
$timeline = $user->statuses()
|
$timeline = $user->statuses()
|
||||||
->whereHas('media')
|
->whereHas('media')
|
||||||
->whereNull('in_reply_to_id')
|
->whereNull('in_reply_to_id')
|
||||||
->orderBy('id','desc')
|
->orderBy('created_at','desc')
|
||||||
->withCount(['comments', 'likes'])
|
->withCount(['comments', 'likes'])
|
||||||
->simplePaginate(21);
|
->simplePaginate(21);
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,39 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App;
|
use App, Auth;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\{Follower, Status, User};
|
||||||
|
|
||||||
class SiteController extends Controller
|
class SiteController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public function home()
|
||||||
|
{
|
||||||
|
if(Auth::check()) {
|
||||||
|
return $this->homeTimeline();
|
||||||
|
} else {
|
||||||
|
return $this->homeGuest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function homeGuest()
|
||||||
|
{
|
||||||
|
return view('site.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function homeTimeline()
|
||||||
|
{
|
||||||
|
// TODO: Use redis for timelines
|
||||||
|
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
|
||||||
|
$following->push(Auth::user()->profile->id);
|
||||||
|
$timeline = Status::whereIn('profile_id', $following)
|
||||||
|
->orderBy('id','desc')
|
||||||
|
->withCount(['comments', 'likes', 'shares'])
|
||||||
|
->simplePaginate(10);
|
||||||
|
return view('timeline.template', compact('timeline'));
|
||||||
|
}
|
||||||
|
|
||||||
public function changeLocale(Request $request, $locale)
|
public function changeLocale(Request $request, $locale)
|
||||||
{
|
{
|
||||||
if(!App::isLocale($locale)) {
|
if(!App::isLocale($locale)) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ class EmailVerificationCheck
|
||||||
if($request->user() &&
|
if($request->user() &&
|
||||||
config('pixelfed.enforce_email_verification') &&
|
config('pixelfed.enforce_email_verification') &&
|
||||||
is_null($request->user()->email_verified_at) &&
|
is_null($request->user()->email_verified_at) &&
|
||||||
!$request->is('i/verify-email') && !$request->is('login') &&
|
!$request->is('i/verify-email') && !$request->is('log*') &&
|
||||||
!$request->is('i/confirm-email/*')
|
!$request->is('i/confirm-email/*')
|
||||||
) {
|
) {
|
||||||
return redirect('/i/verify-email');
|
return redirect('/i/verify-email');
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace App;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class Report extends Model
|
class ImportJob extends Model
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
43
app/Jobs/InboxPipeline/InboxWorker.php
Normal file
43
app/Jobs/InboxPipeline/InboxWorker.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\InboxPipeline;
|
||||||
|
|
||||||
|
use App\Profile;
|
||||||
|
use App\Util\ActivityPub\Inbox;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
|
||||||
|
class InboxWorker implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $request;
|
||||||
|
protected $profile;
|
||||||
|
protected $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct($request, Profile $profile, $payload)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->profile = $profile;
|
||||||
|
$this->payload = $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
(new Inbox($this->request, $this->profile, $this->payload))->handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
app/Jobs/InboxPipeline/SharedInboxWorker.php
Normal file
41
app/Jobs/InboxPipeline/SharedInboxWorker.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\InboxPipeline;
|
||||||
|
|
||||||
|
use App\Profile;
|
||||||
|
use App\Util\ActivityPub\Inbox;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
|
||||||
|
class SharedInboxWorker implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $request;
|
||||||
|
protected $profile;
|
||||||
|
protected $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct($request, $payload)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->payload = $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
(new Inbox($this->request, null, $this->payload))->handleSharedInbox();
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,11 @@ class LikePipeline implements ShouldQueue
|
||||||
$status = $this->like->status;
|
$status = $this->like->status;
|
||||||
$actor = $this->like->actor;
|
$actor = $this->like->actor;
|
||||||
|
|
||||||
|
if($status->url !== null) {
|
||||||
|
// Ignore notifications to remote statuses
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$exists = Notification::whereProfileId($status->profile_id)
|
$exists = Notification::whereProfileId($status->profile_id)
|
||||||
->whereActorId($actor->id)
|
->whereActorId($actor->id)
|
||||||
->whereAction('like')
|
->whereAction('like')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App\Jobs\StatusPipeline;
|
namespace App\Jobs\StatusPipeline;
|
||||||
|
|
||||||
use Cache;
|
use DB, Cache;
|
||||||
use App\{
|
use App\{
|
||||||
Hashtag,
|
Hashtag,
|
||||||
Media,
|
Media,
|
||||||
|
@ -68,12 +68,14 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
|
|
||||||
public function storeEntities()
|
public function storeEntities()
|
||||||
{
|
{
|
||||||
$status = $this->status;
|
|
||||||
$this->storeHashtags();
|
$this->storeHashtags();
|
||||||
$this->storeMentions();
|
$this->storeMentions();
|
||||||
|
DB::transaction(function () {
|
||||||
|
$status = $this->status;
|
||||||
$status->rendered = $this->autolink;
|
$status->rendered = $this->autolink;
|
||||||
$status->entities = json_encode($this->entities);
|
$status->entities = json_encode($this->entities);
|
||||||
$status->save();
|
$status->save();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storeHashtags()
|
public function storeHashtags()
|
||||||
|
@ -82,17 +84,15 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
|
|
||||||
foreach($tags as $tag) {
|
foreach($tags as $tag) {
|
||||||
|
DB::transaction(function () use ($status, $tag) {
|
||||||
$slug = str_slug($tag);
|
$slug = str_slug($tag);
|
||||||
|
$hashtag = Hashtag::firstOrCreate(
|
||||||
$htag = Hashtag::firstOrCreate(
|
['name' => $tag, 'slug' => $slug]
|
||||||
['name' => $tag],
|
|
||||||
['slug' => $slug]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
StatusHashtag::firstOrCreate(
|
StatusHashtag::firstOrCreate(
|
||||||
['status_id' => $status->id],
|
['status_id' => $status->id, 'hashtag_id' => $hashtag->id]
|
||||||
['hashtag_id' => $htag->id]
|
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,16 +102,18 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
|
|
||||||
foreach($mentions as $mention) {
|
foreach($mentions as $mention) {
|
||||||
$mentioned = Profile::whereUsername($mention)->first();
|
$mentioned = Profile::whereUsername($mention)->firstOrFail();
|
||||||
|
|
||||||
if(empty($mentioned) || !isset($mentioned->id)) {
|
if(empty($mentioned) || !isset($mentioned->id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DB::transaction(function () use ($status, $mentioned) {
|
||||||
$m = new Mention;
|
$m = new Mention;
|
||||||
$m->status_id = $status->id;
|
$m->status_id = $status->id;
|
||||||
$m->profile_id = $mentioned->id;
|
$m->profile_id = $mentioned->id;
|
||||||
$m->save();
|
$m->save();
|
||||||
|
});
|
||||||
|
|
||||||
MentionPipeline::dispatch($status, $m);
|
MentionPipeline::dispatch($status, $m);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,11 @@ class Media extends Model
|
||||||
$url = Storage::url($path);
|
$url = Storage::url($path);
|
||||||
return url($url);
|
return url($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function thumbnailUrl()
|
||||||
|
{
|
||||||
|
$path = $this->thumbnail_path;
|
||||||
|
$url = Storage::url($path);
|
||||||
|
return url($url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\{Profile, User};
|
use App\{Profile, User, UserSetting};
|
||||||
use App\Jobs\AvatarPipeline\CreateAvatar;
|
use App\Jobs\AvatarPipeline\CreateAvatar;
|
||||||
|
|
||||||
class UserObserver
|
class UserObserver
|
||||||
|
@ -36,6 +36,12 @@ class UserObserver
|
||||||
|
|
||||||
CreateAvatar::dispatch($profile);
|
CreateAvatar::dispatch($profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(empty($user->settings)) {
|
||||||
|
$settings = new UserSetting;
|
||||||
|
$settings->user_id = $user->id;
|
||||||
|
$settings->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -29,6 +29,15 @@ class Profile extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
public function url($suffix = '')
|
public function url($suffix = '')
|
||||||
|
{
|
||||||
|
if($this->remote_url) {
|
||||||
|
return $this->remote_url;
|
||||||
|
} else {
|
||||||
|
return url($this->username . $suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function localUrl($suffix = '')
|
||||||
{
|
{
|
||||||
return url($this->username . $suffix);
|
return url($this->username . $suffix);
|
||||||
}
|
}
|
||||||
|
@ -124,4 +133,9 @@ class Profile extends Model
|
||||||
$url = url(Storage::url($this->avatar->media_path ?? 'public/avatars/default.png'));
|
$url = url(Storage::url($this->avatar->media_path ?? 'public/avatars/default.png'));
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function statusCount()
|
||||||
|
{
|
||||||
|
return $this->statuses()->whereHas('media')->count();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@ class EventServiceProvider extends ServiceProvider
|
||||||
'App\Events\Event' => [
|
'App\Events\Event' => [
|
||||||
'App\Listeners\EventListener',
|
'App\Listeners\EventListener',
|
||||||
],
|
],
|
||||||
|
'auth.login' => [
|
||||||
|
'App\Events\AuthLoginEvent',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
10
app/ReportComment.php
Normal file
10
app/ReportComment.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ReportComment extends Model
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
10
app/ReportLog.php
Normal file
10
app/ReportLog.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ReportLog extends Model
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use Storage;
|
use Auth, Storage;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
@ -71,15 +71,39 @@ class Status extends Model
|
||||||
return $this->hasMany(Like::class);
|
return $this->hasMany(Like::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function liked() : bool
|
||||||
|
{
|
||||||
|
$profile = Auth::user()->profile;
|
||||||
|
return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
|
||||||
|
}
|
||||||
|
|
||||||
public function comments()
|
public function comments()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Status::class, 'in_reply_to_id');
|
return $this->hasMany(Status::class, 'in_reply_to_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function bookmarked()
|
||||||
|
{
|
||||||
|
$profile = Auth::user()->profile;
|
||||||
|
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shares()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Status::class, 'reblog_of_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shared() : bool
|
||||||
|
{
|
||||||
|
$profile = Auth::user()->profile;
|
||||||
|
return Status::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
|
||||||
|
}
|
||||||
|
|
||||||
public function parent()
|
public function parent()
|
||||||
{
|
{
|
||||||
if(!empty($this->in_reply_to_id)) {
|
$parent = $this->in_reply_to_id ?? $this->reblog_of_id;
|
||||||
return Status::findOrFail($this->in_reply_to_id);
|
if(!empty($parent)) {
|
||||||
|
return Status::findOrFail($parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +124,23 @@ class Status extends Model
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function mentions()
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(
|
||||||
|
Profile::class,
|
||||||
|
Mention::class,
|
||||||
|
'status_id',
|
||||||
|
'id',
|
||||||
|
'id',
|
||||||
|
'profile_id'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reportUrl()
|
||||||
|
{
|
||||||
|
return route('report.form') . "?type=post&id={$this->id}";
|
||||||
|
}
|
||||||
|
|
||||||
public function toActivityStream()
|
public function toActivityStream()
|
||||||
{
|
{
|
||||||
$media = $this->media;
|
$media = $this->media;
|
||||||
|
|
|
@ -6,5 +6,5 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class StatusHashtag extends Model
|
class StatusHashtag extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = ['status_id', 'hashtag_id'];
|
public $fillable = ['status_id', 'hashtag_id'];
|
||||||
}
|
}
|
||||||
|
|
33
app/Transformer/Api/AccountTransformer.php
Normal file
33
app/Transformer/Api/AccountTransformer.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
|
use App\Profile;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
|
class AccountTransformer extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform(Profile $profile)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $profile->id,
|
||||||
|
'username' => $profile->username,
|
||||||
|
'acct' => $profile->username,
|
||||||
|
'display_name' => $profile->name,
|
||||||
|
'locked' => (bool) $profile->is_private,
|
||||||
|
'created_at' => $profile->created_at->format('c'),
|
||||||
|
'followers_count' => $profile->followerCount(),
|
||||||
|
'following_count' => $profile->followingCount(),
|
||||||
|
'statuses_count' => $profile->statusCount(),
|
||||||
|
'note' => $profile->bio,
|
||||||
|
'url' => $profile->url(),
|
||||||
|
'avatar' => $profile->avatarUrl(),
|
||||||
|
'avatar_static' => $profile->avatarUrl(),
|
||||||
|
'header' => '',
|
||||||
|
'header_static' => '',
|
||||||
|
'moved' => null,
|
||||||
|
'fields' => null,
|
||||||
|
'bot' => null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
16
app/Transformer/Api/ApplicationTransformer.php
Normal file
16
app/Transformer/Api/ApplicationTransformer.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
|
class ApplicationTransformer extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => '',
|
||||||
|
'website' => null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
18
app/Transformer/Api/HashtagTransformer.php
Normal file
18
app/Transformer/Api/HashtagTransformer.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
|
use App\Hashtag;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
|
class HashtagTransformer extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform(Hashtag $hashtag)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $hashtag->name,
|
||||||
|
'url' => $hashtag->url(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
24
app/Transformer/Api/MediaTransformer.php
Normal file
24
app/Transformer/Api/MediaTransformer.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
|
use App\Media;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
|
class MediaTransformer extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform(Media $media)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $media->id,
|
||||||
|
'type' => 'image',
|
||||||
|
'url' => $media->url(),
|
||||||
|
'remote_url' => null,
|
||||||
|
'preview_url' => $media->thumbnailUrl(),
|
||||||
|
'text_url' => null,
|
||||||
|
'meta' => null,
|
||||||
|
'description' => null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
19
app/Transformer/Api/MentionTransformer.php
Normal file
19
app/Transformer/Api/MentionTransformer.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
|
use App\Profile;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
|
class MentionTransformer extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform(Profile $profile)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $profile->id,
|
||||||
|
'url' => $profile->url(),
|
||||||
|
'username' => $profile->username,
|
||||||
|
'acct' => $profile->username,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
69
app/Transformer/Api/StatusTransformer.php
Normal file
69
app/Transformer/Api/StatusTransformer.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
|
use App\Status;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
|
class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
protected $defaultIncludes = [
|
||||||
|
'account',
|
||||||
|
'mentions',
|
||||||
|
'media_attachments',
|
||||||
|
'tags'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function transform(Status $status)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $status->id,
|
||||||
|
'uri' => $status->url(),
|
||||||
|
'url' => $status->url(),
|
||||||
|
'in_reply_to_id' => $status->in_reply_to_id,
|
||||||
|
'in_reply_to_account_id' => $status->in_reply_to_profile_id,
|
||||||
|
|
||||||
|
// TODO: fixme
|
||||||
|
'reblog' => null,
|
||||||
|
|
||||||
|
'content' => "<p>$status->rendered</p>",
|
||||||
|
'created_at' => $status->created_at->format('c'),
|
||||||
|
'emojis' => [],
|
||||||
|
'reblogs_count' => $status->shares()->count(),
|
||||||
|
'favourites_count' => $status->likes()->count(),
|
||||||
|
'reblogged' => $status->shared(),
|
||||||
|
'favourited' => $status->liked(),
|
||||||
|
'muted' => null,
|
||||||
|
'sensitive' => (bool) $status->is_nsfw,
|
||||||
|
'spoiler_text' => '',
|
||||||
|
'visibility' => $status->visibility,
|
||||||
|
'application' => null,
|
||||||
|
'language' => null,
|
||||||
|
'pinned' => null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function includeAccount(Status $status)
|
||||||
|
{
|
||||||
|
$account = $status->profile;
|
||||||
|
return $this->item($account, new AccountTransformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function includeMentions(Status $status)
|
||||||
|
{
|
||||||
|
$mentions = $status->mentions;
|
||||||
|
return $this->collection($mentions, new MentionTransformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function includeMediaAttachments(Status $status)
|
||||||
|
{
|
||||||
|
$media = $status->media;
|
||||||
|
return $this->collection($media, new MediaTransformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function includeTags(Status $status)
|
||||||
|
{
|
||||||
|
$tags = $status->hashtags;
|
||||||
|
return $this->collection($tags, new HashtagTransformer);
|
||||||
|
}
|
||||||
|
}
|
10
app/UserFilter.php
Normal file
10
app/UserFilter.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class UserFilter extends Model
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
10
app/UserSetting.php
Normal file
10
app/UserSetting.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class UserSetting extends Model
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"99designs/http-signatures-guzzlehttp": "^2.0",
|
"99designs/http-signatures-guzzlehttp": "^2.0",
|
||||||
|
"beyondcode/laravel-self-diagnosis": "^0.4.0",
|
||||||
"bitverse/identicon": "^1.1",
|
"bitverse/identicon": "^1.1",
|
||||||
"doctrine/dbal": "^2.7",
|
"doctrine/dbal": "^2.7",
|
||||||
"fideloper/proxy": "^4.0",
|
"fideloper/proxy": "^4.0",
|
||||||
|
@ -15,9 +16,13 @@
|
||||||
"kitetail/zttp": "^0.3.0",
|
"kitetail/zttp": "^0.3.0",
|
||||||
"laravel/framework": "5.6.*",
|
"laravel/framework": "5.6.*",
|
||||||
"laravel/horizon": "^1.2",
|
"laravel/horizon": "^1.2",
|
||||||
|
"laravel/passport": "^6.0",
|
||||||
"laravel/tinker": "^1.0",
|
"laravel/tinker": "^1.0",
|
||||||
"league/fractal": "^0.17.0",
|
"moontoast/math": "^1.1",
|
||||||
"phpseclib/phpseclib": "~2.0",
|
"phpseclib/phpseclib": "~2.0",
|
||||||
|
"pixelfed/dotenv-editor": "^2.0",
|
||||||
|
"pixelfed/fractal": "^0.18.0",
|
||||||
|
"pixelfed/google2fa-laravel": "^2.0",
|
||||||
"predis/predis": "^1.1",
|
"predis/predis": "^1.1",
|
||||||
"spatie/laravel-backup": "^5.0.0",
|
"spatie/laravel-backup": "^5.0.0",
|
||||||
"spatie/laravel-image-optimizer": "^1.1",
|
"spatie/laravel-image-optimizer": "^1.1",
|
||||||
|
@ -25,6 +30,7 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"barryvdh/laravel-debugbar": "^3.1",
|
"barryvdh/laravel-debugbar": "^3.1",
|
||||||
|
"beyondcode/laravel-er-diagram-generator": "^0.2.2",
|
||||||
"filp/whoops": "^2.0",
|
"filp/whoops": "^2.0",
|
||||||
"fzaninotto/faker": "^1.4",
|
"fzaninotto/faker": "^1.4",
|
||||||
"mockery/mockery": "^1.0",
|
"mockery/mockery": "^1.0",
|
||||||
|
|
1457
composer.lock
generated
1457
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -49,5 +49,5 @@ return [
|
||||||
* If set to `true` all output of the optimizer binaries will be appended to the default log.
|
* If set to `true` all output of the optimizer binaries will be appended to the default log.
|
||||||
* You can also set this to a class that implements `Psr\Log\LoggerInterface`.
|
* You can also set this to a class that implements `Psr\Log\LoggerInterface`.
|
||||||
*/
|
*/
|
||||||
'log_optimizer_activity' => true,
|
'log_optimizer_activity' => false,
|
||||||
];
|
];
|
||||||
|
|
|
@ -77,6 +77,17 @@ return [
|
||||||
|
|
||||||
'activitypub_enabled' => env('ACTIVITY_PUB', false),
|
'activitypub_enabled' => env('ACTIVITY_PUB', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Account file size limit
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Update the max account size, the per user limit of files in kb.
|
||||||
|
| This applies to local and remote users. Old remote posts may be GC.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'max_account_size' => env('MAX_ACCOUNT_SIZE', 100000),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Photo file size limit
|
| Photo file size limit
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateWebSubsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('web_subs', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('follower_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('following_id')->unsigned()->index();
|
||||||
|
$table->string('profile_url')->index();
|
||||||
|
$table->timestamp('approved_at')->nullable();
|
||||||
|
$table->unique(['follower_id', 'following_id', 'profile_url']);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('web_subs');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateImportJobsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('import_jobs', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->bigInteger('profile_id')->unsigned();
|
||||||
|
$table->string('service')->default('instagram');
|
||||||
|
$table->string('uuid')->nullable();
|
||||||
|
$table->string('storage_path')->nullable();
|
||||||
|
$table->tinyInteger('stage')->unsigned()->default(0);
|
||||||
|
$table->text('media_json')->nullable();
|
||||||
|
$table->timestamp('completed_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('import_jobs');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateReportCommentsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('report_comments', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->bigInteger('report_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('profile_id')->unsigned();
|
||||||
|
$table->bigInteger('user_id')->unsigned();
|
||||||
|
$table->text('comment');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('report_comments');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateReportLogsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('report_logs', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->bigInteger('profile_id')->unsigned();
|
||||||
|
$table->bigInteger('item_id')->unsigned()->nullable();
|
||||||
|
$table->string('item_type')->nullable();
|
||||||
|
$table->string('action')->nullable();
|
||||||
|
$table->boolean('system_message')->default(false);
|
||||||
|
$table->json('metadata')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('report_logs');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateAccountLogsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('account_logs', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('user_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('item_id')->unsigned()->nullable();
|
||||||
|
$table->string('item_type')->nullable();
|
||||||
|
$table->string('action')->nullable();
|
||||||
|
$table->string('message')->nullable();
|
||||||
|
$table->string('link')->nullable();
|
||||||
|
$table->string('ip_address')->nullable();
|
||||||
|
$table->string('user_agent')->nullable();
|
||||||
|
$table->json('metadata')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('account_logs');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateUserSettingsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('user_settings', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('user_id')->unsigned()->unique();
|
||||||
|
$table->string('role')->default('user');
|
||||||
|
$table->boolean('crawlable')->default(true);
|
||||||
|
$table->boolean('show_guests')->default(true);
|
||||||
|
$table->boolean('show_discover')->default(true);
|
||||||
|
$table->boolean('public_dm')->default(false);
|
||||||
|
$table->boolean('hide_cw_search')->default(true);
|
||||||
|
$table->boolean('hide_blocked_search')->default(true);
|
||||||
|
$table->boolean('always_show_cw')->default(false);
|
||||||
|
$table->boolean('compose_media_descriptions')->default(false);
|
||||||
|
$table->boolean('reduce_motion')->default(false);
|
||||||
|
$table->boolean('optimize_screen_reader')->default(false);
|
||||||
|
$table->boolean('high_contrast_mode')->default(false);
|
||||||
|
$table->boolean('video_autoplay')->default(false);
|
||||||
|
$table->boolean('send_email_new_follower')->default(false);
|
||||||
|
$table->boolean('send_email_new_follower_request')->default(true);
|
||||||
|
$table->boolean('send_email_on_share')->default(false);
|
||||||
|
$table->boolean('send_email_on_like')->default(false);
|
||||||
|
$table->boolean('send_email_on_mention')->default(false);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_settings');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class Add2faToUsersTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->boolean('2fa_enabled')->default(false);
|
||||||
|
$table->string('2fa_secret')->nullable();
|
||||||
|
$table->json('2fa_backup_codes')->nullable();
|
||||||
|
$table->timestamp('2fa_setup_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('2fa_enabled');
|
||||||
|
$table->dropColumn('2fa_secret');
|
||||||
|
$table->dropColumn('2fa_backup_codes');
|
||||||
|
$table->dropColumn('2fa_setup_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateUserFiltersTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('user_filters', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('user_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('filterable_id')->unsigned();
|
||||||
|
$table->string('filterable_type');
|
||||||
|
$table->string('filter_type')->default('block')->index();
|
||||||
|
$table->unique([
|
||||||
|
'user_id',
|
||||||
|
'filterable_id',
|
||||||
|
'filterable_type',
|
||||||
|
'filter_type'
|
||||||
|
], 'filter_unique');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_filters');
|
||||||
|
}
|
||||||
|
}
|
BIN
public/img/pixelfed-icon-color.svg
Normal file
BIN
public/img/pixelfed-icon-color.svg
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
107
resources/assets/js/components/passport/AuthorizedClients.vue
Normal file
107
resources/assets/js/components/passport/AuthorizedClients.vue
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<style scoped>
|
||||||
|
.action-link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="tokens.length > 0">
|
||||||
|
<div class="card card-default mb-4">
|
||||||
|
<div class="card-header font-weight-bold bg-white">Authorized Applications</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Authorized Tokens -->
|
||||||
|
<table class="table table-borderless mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Scopes</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="token in tokens">
|
||||||
|
<!-- Client Name -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
{{ token.client.name }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Scopes -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
<span v-if="token.scopes.length > 0">
|
||||||
|
{{ token.scopes.join(', ') }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Revoke Button -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
<a class="action-link text-danger" @click="revoke(token)">
|
||||||
|
Revoke
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
/*
|
||||||
|
* The component's data.
|
||||||
|
*/
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tokens: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the component (Vue 1.x).
|
||||||
|
*/
|
||||||
|
ready() {
|
||||||
|
this.prepareComponent();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the component (Vue 2.x).
|
||||||
|
*/
|
||||||
|
mounted() {
|
||||||
|
this.prepareComponent();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Prepare the component (Vue 2.x).
|
||||||
|
*/
|
||||||
|
prepareComponent() {
|
||||||
|
this.getTokens();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all of the authorized tokens for the user.
|
||||||
|
*/
|
||||||
|
getTokens() {
|
||||||
|
axios.get('/oauth/tokens')
|
||||||
|
.then(response => {
|
||||||
|
this.tokens = response.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke the given token.
|
||||||
|
*/
|
||||||
|
revoke(token) {
|
||||||
|
axios.delete('/oauth/tokens/' + token.id)
|
||||||
|
.then(response => {
|
||||||
|
this.getTokens();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
350
resources/assets/js/components/passport/Clients.vue
Normal file
350
resources/assets/js/components/passport/Clients.vue
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
<style scoped>
|
||||||
|
.action-link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="card card-default mb-4">
|
||||||
|
<div class="card-header font-weight-bold bg-white">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<span>
|
||||||
|
OAuth Clients
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<a class="action-link" tabindex="-1" @click="showCreateClientForm">
|
||||||
|
Create New Client
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Current Clients -->
|
||||||
|
<p class="mb-0" v-if="clients.length === 0">
|
||||||
|
You have not created any OAuth clients.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table table-borderless mb-0" v-if="clients.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Client ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Secret</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="client in clients">
|
||||||
|
<!-- ID -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
{{ client.id }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
{{ client.name }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Secret -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
<code>{{ client.secret }}</code>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Edit Button -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
<a class="action-link" tabindex="-1" @click="edit(client)">
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Delete Button -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
<a class="action-link text-danger" @click="destroy(client)">
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create Client Modal -->
|
||||||
|
<div class="modal fade" id="modal-create-client" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">
|
||||||
|
Create Client
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Form Errors -->
|
||||||
|
<div class="alert alert-danger" v-if="createForm.errors.length > 0">
|
||||||
|
<p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li v-for="error in createForm.errors">
|
||||||
|
{{ error }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create Client Form -->
|
||||||
|
<form role="form">
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-md-3 col-form-label">Name</label>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="create-client-name" type="text" class="form-control" autocomplete="off"
|
||||||
|
@keyup.enter="store" v-model="createForm.name">
|
||||||
|
|
||||||
|
<span class="form-text text-muted">
|
||||||
|
Something your users will recognize and trust.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Redirect URL -->
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-md-3 col-form-label">Redirect URL</label>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control" name="redirect"
|
||||||
|
@keyup.enter="store" v-model="createForm.redirect">
|
||||||
|
|
||||||
|
<span class="form-text text-muted">
|
||||||
|
Your application's authorization callback URL.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Actions -->
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary font-weight-bold" data-dismiss="modal">Close</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary font-weight-bold" @click="store">
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Client Modal -->
|
||||||
|
<div class="modal fade" id="modal-edit-client" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">
|
||||||
|
Edit Client
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Form Errors -->
|
||||||
|
<div class="alert alert-danger" v-if="editForm.errors.length > 0">
|
||||||
|
<p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li v-for="error in editForm.errors">
|
||||||
|
{{ error }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Client Form -->
|
||||||
|
<form role="form">
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-md-3 col-form-label">Name</label>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input id="edit-client-name" type="text" class="form-control"
|
||||||
|
@keyup.enter="update" v-model="editForm.name">
|
||||||
|
|
||||||
|
<span class="form-text text-muted">
|
||||||
|
Something your users will recognize and trust.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Redirect URL -->
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-md-3 col-form-label">Redirect URL</label>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control" name="redirect"
|
||||||
|
@keyup.enter="update" v-model="editForm.redirect">
|
||||||
|
|
||||||
|
<span class="form-text text-muted">
|
||||||
|
Your application's authorization callback URL.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Actions -->
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary" @click="update">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
/*
|
||||||
|
* The component's data.
|
||||||
|
*/
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
clients: [],
|
||||||
|
|
||||||
|
createForm: {
|
||||||
|
errors: [],
|
||||||
|
name: '',
|
||||||
|
redirect: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
editForm: {
|
||||||
|
errors: [],
|
||||||
|
name: '',
|
||||||
|
redirect: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the component (Vue 1.x).
|
||||||
|
*/
|
||||||
|
ready() {
|
||||||
|
this.prepareComponent();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the component (Vue 2.x).
|
||||||
|
*/
|
||||||
|
mounted() {
|
||||||
|
this.prepareComponent();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Prepare the component.
|
||||||
|
*/
|
||||||
|
prepareComponent() {
|
||||||
|
this.getClients();
|
||||||
|
|
||||||
|
$('#modal-create-client').on('shown.bs.modal', () => {
|
||||||
|
$('#create-client-name').focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#modal-edit-client').on('shown.bs.modal', () => {
|
||||||
|
$('#edit-client-name').focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all of the OAuth clients for the user.
|
||||||
|
*/
|
||||||
|
getClients() {
|
||||||
|
axios.get('/oauth/clients')
|
||||||
|
.then(response => {
|
||||||
|
this.clients = response.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating new clients.
|
||||||
|
*/
|
||||||
|
showCreateClientForm() {
|
||||||
|
$('#modal-create-client').modal('show');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new OAuth client for the user.
|
||||||
|
*/
|
||||||
|
store() {
|
||||||
|
this.persistClient(
|
||||||
|
'post', '/oauth/clients',
|
||||||
|
this.createForm, '#modal-create-client'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the given client.
|
||||||
|
*/
|
||||||
|
edit(client) {
|
||||||
|
this.editForm.id = client.id;
|
||||||
|
this.editForm.name = client.name;
|
||||||
|
this.editForm.redirect = client.redirect;
|
||||||
|
|
||||||
|
$('#modal-edit-client').modal('show');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the client being edited.
|
||||||
|
*/
|
||||||
|
update() {
|
||||||
|
this.persistClient(
|
||||||
|
'put', '/oauth/clients/' + this.editForm.id,
|
||||||
|
this.editForm, '#modal-edit-client'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the client to storage using the given form.
|
||||||
|
*/
|
||||||
|
persistClient(method, uri, form, modal) {
|
||||||
|
form.errors = [];
|
||||||
|
|
||||||
|
axios[method](uri, form)
|
||||||
|
.then(response => {
|
||||||
|
this.getClients();
|
||||||
|
|
||||||
|
form.name = '';
|
||||||
|
form.redirect = '';
|
||||||
|
form.errors = [];
|
||||||
|
|
||||||
|
$(modal).modal('hide');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (typeof error.response.data === 'object') {
|
||||||
|
form.errors = _.flatten(_.toArray(error.response.data.errors));
|
||||||
|
} else {
|
||||||
|
form.errors = ['Something went wrong. Please try again.'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the given client.
|
||||||
|
*/
|
||||||
|
destroy(client) {
|
||||||
|
axios.delete('/oauth/clients/' + client.id)
|
||||||
|
.then(response => {
|
||||||
|
this.getClients();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
298
resources/assets/js/components/passport/PersonalAccessTokens.vue
Normal file
298
resources/assets/js/components/passport/PersonalAccessTokens.vue
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
<style scoped>
|
||||||
|
.action-link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div class="card card-default mb-4">
|
||||||
|
<div class="card-header font-weight-bold bg-white">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<span>
|
||||||
|
Personal Access Tokens
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<a class="action-link" tabindex="-1" @click="showCreateTokenForm">
|
||||||
|
Create New Token
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- No Tokens Notice -->
|
||||||
|
<p class="mb-0" v-if="tokens.length === 0">
|
||||||
|
You have not created any personal access tokens.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Personal Access Tokens -->
|
||||||
|
<table class="table table-borderless mb-0" v-if="tokens.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="token in tokens">
|
||||||
|
<!-- Client Name -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
{{ token.name }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Delete Button -->
|
||||||
|
<td style="vertical-align: middle;">
|
||||||
|
<a class="action-link text-danger" @click="revoke(token)">
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create Token Modal -->
|
||||||
|
<div class="modal fade" id="modal-create-token" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">
|
||||||
|
Create Token
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Form Errors -->
|
||||||
|
<div class="alert alert-danger" v-if="form.errors.length > 0">
|
||||||
|
<p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li v-for="error in form.errors">
|
||||||
|
{{ error }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create Token Form -->
|
||||||
|
<form role="form" @submit.prevent="store">
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-md-4 col-form-label">Name</label>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input id="create-token-name" type="text" class="form-control" name="name" v-model="form.name" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scopes -->
|
||||||
|
<div class="form-group row" v-if="scopes.length > 0">
|
||||||
|
<label class="col-md-4 col-form-label">Scopes</label>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div v-for="scope in scopes">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
@click="toggleScope(scope.id)"
|
||||||
|
:checked="scopeIsAssigned(scope.id)">
|
||||||
|
|
||||||
|
{{ scope.id }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Actions -->
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary font-weight-bold" data-dismiss="modal">Close</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary font-weight-bold" @click="store">
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Access Token Modal -->
|
||||||
|
<div class="modal fade" id="modal-access-token" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">
|
||||||
|
Personal Access Token
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Here is your new personal access token. This is the only time it will be shown so don't lose it!
|
||||||
|
You may now use this token to make API requests.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<textarea class="form-control" rows="10">{{ accessToken }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Actions -->
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
/*
|
||||||
|
* The component's data.
|
||||||
|
*/
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
accessToken: null,
|
||||||
|
|
||||||
|
tokens: [],
|
||||||
|
scopes: [],
|
||||||
|
|
||||||
|
form: {
|
||||||
|
name: '',
|
||||||
|
scopes: [],
|
||||||
|
errors: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the component (Vue 1.x).
|
||||||
|
*/
|
||||||
|
ready() {
|
||||||
|
this.prepareComponent();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the component (Vue 2.x).
|
||||||
|
*/
|
||||||
|
mounted() {
|
||||||
|
this.prepareComponent();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Prepare the component.
|
||||||
|
*/
|
||||||
|
prepareComponent() {
|
||||||
|
this.getTokens();
|
||||||
|
this.getScopes();
|
||||||
|
|
||||||
|
$('#modal-create-token').on('shown.bs.modal', () => {
|
||||||
|
$('#create-token-name').focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all of the personal access tokens for the user.
|
||||||
|
*/
|
||||||
|
getTokens() {
|
||||||
|
axios.get('/oauth/personal-access-tokens')
|
||||||
|
.then(response => {
|
||||||
|
this.tokens = response.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all of the available scopes.
|
||||||
|
*/
|
||||||
|
getScopes() {
|
||||||
|
axios.get('/oauth/scopes')
|
||||||
|
.then(response => {
|
||||||
|
this.scopes = response.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating new tokens.
|
||||||
|
*/
|
||||||
|
showCreateTokenForm() {
|
||||||
|
$('#modal-create-token').modal('show');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new personal access token.
|
||||||
|
*/
|
||||||
|
store() {
|
||||||
|
this.accessToken = null;
|
||||||
|
|
||||||
|
this.form.errors = [];
|
||||||
|
|
||||||
|
axios.post('/oauth/personal-access-tokens', this.form)
|
||||||
|
.then(response => {
|
||||||
|
this.form.name = '';
|
||||||
|
this.form.scopes = [];
|
||||||
|
this.form.errors = [];
|
||||||
|
|
||||||
|
this.tokens.push(response.data.token);
|
||||||
|
|
||||||
|
this.showAccessToken(response.data.accessToken);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (typeof error.response.data === 'object') {
|
||||||
|
this.form.errors = _.flatten(_.toArray(error.response.data.errors));
|
||||||
|
} else {
|
||||||
|
this.form.errors = ['Something went wrong. Please try again.'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the given scope in the list of assigned scopes.
|
||||||
|
*/
|
||||||
|
toggleScope(scope) {
|
||||||
|
if (this.scopeIsAssigned(scope)) {
|
||||||
|
this.form.scopes = _.reject(this.form.scopes, s => s == scope);
|
||||||
|
} else {
|
||||||
|
this.form.scopes.push(scope);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given scope has been assigned to the token.
|
||||||
|
*/
|
||||||
|
scopeIsAssigned(scope) {
|
||||||
|
return _.indexOf(this.form.scopes, scope) >= 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the given access token to the user.
|
||||||
|
*/
|
||||||
|
showAccessToken(accessToken) {
|
||||||
|
$('#modal-create-token').modal('hide');
|
||||||
|
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
|
||||||
|
$('#modal-access-token').modal('show');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke the given token.
|
||||||
|
*/
|
||||||
|
revoke(token) {
|
||||||
|
axios.delete('/oauth/personal-access-tokens/' + token.id)
|
||||||
|
.then(response => {
|
||||||
|
this.getTokens();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,8 +1,8 @@
|
||||||
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
|
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand d-flex align-items-center" href="{{ url('/timeline') }}" title="Logo">
|
<a class="navbar-brand d-flex align-items-center" href="{{ url('/timeline') }}" title="Logo">
|
||||||
<img src="/img/pixelfed-icon-black.svg" height="60px" class="p-2">
|
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2">
|
||||||
<span class="h4 font-weight-bold mb-0">{{ config('app.name', 'Laravel') }}</span>
|
<span class="font-weight-bold mb-0" style="font-size:20px;">{{ config('app.name', 'Laravel') }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
@ -23,7 +23,9 @@
|
||||||
<a class="nav-link" href="{{route('discover')}}" title="Discover"><i class="far fa-compass fa-lg"></i></a>
|
<a class="nav-link" href="{{route('discover')}}" title="Discover"><i class="far fa-compass fa-lg"></i></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-2">
|
<li class="nav-item px-2">
|
||||||
<a class="nav-link" href="{{route('notifications')}}" title="Notifications"><i class="far fa-heart fa-lg"></i></a>
|
<a class="nav-link" href="{{route('notifications')}}" title="Notifications">
|
||||||
|
<i class="far fa-heart fa-lg"></i>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown px-2">
|
<li class="nav-item dropdown px-2">
|
||||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre title="User Menu">
|
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre title="User Menu">
|
||||||
|
|
|
@ -35,49 +35,6 @@
|
||||||
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address" value="{{Auth::user()->email}}" readonly>
|
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address" value="{{Auth::user()->email}}" readonly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{--<div class="form-group row">
|
|
||||||
<label for="inputPassword3" class="col-sm-3 col-form-label">Password</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="password" class="form-control" id="inputPassword3" placeholder="Password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<fieldset class="form-group">
|
|
||||||
<div class="row">
|
|
||||||
<legend class="col-form-label col-sm-3 pt-0">Radios</legend>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios1" value="option1" checked>
|
|
||||||
<label class="form-check-label" for="gridRadios1">
|
|
||||||
First radio
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios2" value="option2">
|
|
||||||
<label class="form-check-label" for="gridRadios2">
|
|
||||||
Second radio
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check disabled">
|
|
||||||
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios3" value="option3" disabled>
|
|
||||||
<label class="form-check-label" for="gridRadios3">
|
|
||||||
Third disabled radio
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-3">Checkbox</div>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="gridCheck1">
|
|
||||||
<label class="form-check-label" for="gridCheck1">
|
|
||||||
Example checkbox
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>--}}
|
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
|
Loading…
Reference in a new issue