Merge pull request #334 from dansup/frontend-ui-refactor

Frontend ui refactor
pull/341/head
daniel 2018-07-23 13:13:16 -06:00 zatwierdzone przez GitHub
commit 56750833e9
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
49 zmienionych plików z 2921 dodań i 312 usunięć

10
app/AccountLog.php 100644
Wyświetl plik

@ -0,0 +1,10 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AccountLog extends Model
{
//
}

Wyświetl plik

@ -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();
}
}
}

Wyświetl plik

@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
class Hashtag extends Model
{
protected $fillable = ['name','slug'];
public $fillable = ['name','slug'];
public function posts()
{

Wyświetl plik

@ -18,15 +18,24 @@ class AccountController extends Controller
public function notifications(Request $request)
{
$this->validate($request, [
'page' => 'nullable|min:1|max:3'
'page' => 'nullable|min:1|max:3',
'a' => 'nullable|alpha_dash',
]);
$profile = Auth::user()->profile;
$action = $request->input('a');
$timeago = Carbon::now()->subMonths(6);
$notifications = Notification::whereProfileId($profile->id)
->whereDate('created_at', '>', $timeago)
->orderBy('id','desc')
->take(30)
->simplePaginate();
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)
->whereDate('created_at', '>', $timeago)
->orderBy('id','desc')
->simplePaginate(30);
}
return view('account.activity', compact('profile', 'notifications'));
}
@ -38,10 +47,19 @@ class AccountController extends Controller
public function sendVerifyEmail(Request $request)
{
if(EmailVerification::whereUserId(Auth::id())->count() !== 0) {
return redirect()->back()->with('status', 'A verification email has already been sent! Please check your email.');
$timeLimit = Carbon::now()->subDays(1)->toDateTimeString();
$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());
$utoken = hash('sha512', $user->id);
$rtoken = str_random(40);
@ -60,14 +78,15 @@ class AccountController extends Controller
public function confirmVerifyEmail(Request $request, $userToken, $randomToken)
{
$verify = EmailVerification::where(DB::raw('BINARY user_token'), $userToken)
->where(DB::raw('BINARY random_token'), $randomToken)
$verify = EmailVerification::where('user_token', $userToken)
->where('random_token', $randomToken)
->firstOrFail();
if(Auth::id() === $verify->user_id) {
$user = User::find(Auth::id());
$user->email_verified_at = Carbon::now();
$user->save();
return redirect('/timeline');
return redirect('/');
}
}
@ -95,4 +114,5 @@ class AccountController extends Controller
}
return $notifications;
}
}

Wyświetl plik

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Auth;
use App\{AccountLog, User};
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
@ -25,7 +26,7 @@ class LoginController extends Controller
*
* @var string
*/
protected $redirectTo = '/home';
protected $redirectTo = '/';
/**
* Create a new controller instance.
@ -56,4 +57,25 @@ class LoginController extends Controller
$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();
}
}

Wyświetl plik

@ -1,10 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ImportDataController extends Controller
{
//
}

Wyświetl plik

@ -3,7 +3,7 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth, Hashids;
use Auth, Cache, Hashids;
use App\{Like, Profile, Status, User};
use App\Jobs\LikePipeline\LikePipeline;
@ -27,7 +27,7 @@ class LikeController extends Controller
if($status->likes()->whereProfileId($profile->id)->count() !== 0) {
$like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail();
$like->delete();
$like->forceDelete();
$count--;
} else {
$like = new Like;
@ -35,9 +35,15 @@ class LikeController extends Controller
$like->status_id = $status->id;
$like->save();
$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()) {
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];

Wyświetl plik

@ -21,7 +21,6 @@ class ProfileController extends Controller
$mimes = [
'application/activity+json',
'application/ld+json',
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
];
@ -36,7 +35,7 @@ class ProfileController extends Controller
$timeline = $user->statuses()
->whereHas('media')
->whereNull('in_reply_to_id')
->orderBy('id','desc')
->orderBy('created_at','desc')
->withCount(['comments', 'likes'])
->simplePaginate(21);

Wyświetl plik

@ -2,11 +2,39 @@
namespace App\Http\Controllers;
use App;
use App, Auth;
use Illuminate\Http\Request;
use App\{Follower, Status, User};
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)
{
if(!App::isLocale($locale)) {

Wyświetl plik

@ -18,7 +18,7 @@ class EmailVerificationCheck
if($request->user() &&
config('pixelfed.enforce_email_verification') &&
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/*')
) {
return redirect('/i/verify-email');

Wyświetl plik

@ -4,7 +4,7 @@ namespace App;
use Illuminate\Database\Eloquent\Model;
class Report extends Model
class ImportJob extends Model
{
//
}

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -37,6 +37,11 @@ class LikePipeline implements ShouldQueue
$status = $this->like->status;
$actor = $this->like->actor;
if($status->url !== null) {
// Ignore notifications to remote statuses
return;
}
$exists = Notification::whereProfileId($status->profile_id)
->whereActorId($actor->id)
->whereAction('like')

Wyświetl plik

@ -2,7 +2,7 @@
namespace App\Jobs\StatusPipeline;
use Cache;
use DB, Cache;
use App\{
Hashtag,
Media,
@ -68,12 +68,14 @@ class StatusEntityLexer implements ShouldQueue
public function storeEntities()
{
$status = $this->status;
$this->storeHashtags();
$this->storeMentions();
$status->rendered = $this->autolink;
$status->entities = json_encode($this->entities);
$status->save();
DB::transaction(function () {
$status = $this->status;
$status->rendered = $this->autolink;
$status->entities = json_encode($this->entities);
$status->save();
});
}
public function storeHashtags()
@ -82,17 +84,15 @@ class StatusEntityLexer implements ShouldQueue
$status = $this->status;
foreach($tags as $tag) {
$slug = str_slug($tag);
$htag = Hashtag::firstOrCreate(
['name' => $tag],
['slug' => $slug]
);
StatusHashtag::firstOrCreate(
['status_id' => $status->id],
['hashtag_id' => $htag->id]
);
DB::transaction(function () use ($status, $tag) {
$slug = str_slug($tag);
$hashtag = Hashtag::firstOrCreate(
['name' => $tag, 'slug' => $slug]
);
StatusHashtag::firstOrCreate(
['status_id' => $status->id, 'hashtag_id' => $hashtag->id]
);
});
}
}
@ -102,16 +102,18 @@ class StatusEntityLexer implements ShouldQueue
$status = $this->status;
foreach($mentions as $mention) {
$mentioned = Profile::whereUsername($mention)->first();
$mentioned = Profile::whereUsername($mention)->firstOrFail();
if(empty($mentioned) || !isset($mentioned->id)) {
continue;
}
$m = new Mention;
$m->status_id = $status->id;
$m->profile_id = $mentioned->id;
$m->save();
DB::transaction(function () use ($status, $mentioned) {
$m = new Mention;
$m->status_id = $status->id;
$m->profile_id = $mentioned->id;
$m->save();
});
MentionPipeline::dispatch($status, $m);
}

Wyświetl plik

@ -23,4 +23,11 @@ class Media extends Model
$url = Storage::url($path);
return url($url);
}
public function thumbnailUrl()
{
$path = $this->thumbnail_path;
$url = Storage::url($path);
return url($url);
}
}

Wyświetl plik

@ -2,7 +2,7 @@
namespace App\Observers;
use App\{Profile, User};
use App\{Profile, User, UserSetting};
use App\Jobs\AvatarPipeline\CreateAvatar;
class UserObserver
@ -36,6 +36,12 @@ class UserObserver
CreateAvatar::dispatch($profile);
}
if(empty($user->settings)) {
$settings = new UserSetting;
$settings->user_id = $user->id;
$settings->save();
}
}
}

Wyświetl plik

@ -29,6 +29,15 @@ class Profile extends Model
}
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);
}
@ -124,4 +133,9 @@ class Profile extends Model
$url = url(Storage::url($this->avatar->media_path ?? 'public/avatars/default.png'));
return $url;
}
public function statusCount()
{
return $this->statuses()->whereHas('media')->count();
}
}

Wyświetl plik

@ -16,6 +16,9 @@ class EventServiceProvider extends ServiceProvider
'App\Events\Event' => [
'App\Listeners\EventListener',
],
'auth.login' => [
'App\Events\AuthLoginEvent',
],
];
/**

Wyświetl plik

@ -0,0 +1,10 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ReportComment extends Model
{
//
}

10
app/ReportLog.php 100644
Wyświetl plik

@ -0,0 +1,10 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ReportLog extends Model
{
//
}

Wyświetl plik

@ -2,7 +2,7 @@
namespace App;
use Storage;
use Auth, Storage;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -71,15 +71,39 @@ class Status extends Model
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()
{
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()
{
if(!empty($this->in_reply_to_id)) {
return Status::findOrFail($this->in_reply_to_id);
$parent = $this->in_reply_to_id ?? $this->reblog_of_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()
{
$media = $this->media;

Wyświetl plik

@ -6,5 +6,5 @@ use Illuminate\Database\Eloquent\Model;
class StatusHashtag extends Model
{
protected $fillable = ['status_id', 'hashtag_id'];
public $fillable = ['status_id', 'hashtag_id'];
}

Wyświetl plik

@ -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
];
}
}

Wyświetl plik

@ -0,0 +1,16 @@
<?php
namespace App\Transformer\Api;
use League\Fractal;
class ApplicationTransformer extends Fractal\TransformerAbstract
{
public function transform()
{
return [
'name' => '',
'website' => null
];
}
}

Wyświetl plik

@ -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(),
];
}
}

Wyświetl plik

@ -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
];
}
}

Wyświetl plik

@ -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,
];
}
}

Wyświetl plik

@ -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 100644
Wyświetl plik

@ -0,0 +1,10 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UserFilter extends Model
{
//
}

Wyświetl plik

@ -0,0 +1,10 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UserSetting extends Model
{
//
}

Wyświetl plik

@ -7,6 +7,7 @@
"require": {
"php": "^7.1.3",
"99designs/http-signatures-guzzlehttp": "^2.0",
"beyondcode/laravel-self-diagnosis": "^0.4.0",
"bitverse/identicon": "^1.1",
"doctrine/dbal": "^2.7",
"fideloper/proxy": "^4.0",
@ -15,9 +16,13 @@
"kitetail/zttp": "^0.3.0",
"laravel/framework": "5.6.*",
"laravel/horizon": "^1.2",
"laravel/passport": "^6.0",
"laravel/tinker": "^1.0",
"league/fractal": "^0.17.0",
"moontoast/math": "^1.1",
"phpseclib/phpseclib": "~2.0",
"pixelfed/dotenv-editor": "^2.0",
"pixelfed/fractal": "^0.18.0",
"pixelfed/google2fa-laravel": "^2.0",
"predis/predis": "^1.1",
"spatie/laravel-backup": "^5.0.0",
"spatie/laravel-image-optimizer": "^1.1",
@ -25,6 +30,7 @@
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.1",
"beyondcode/laravel-er-diagram-generator": "^0.2.2",
"filp/whoops": "^2.0",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",

1457
composer.lock wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -49,5 +49,5 @@ return [
* 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`.
*/
'log_optimizer_activity' => true,
'log_optimizer_activity' => false,
];

Wyświetl plik

@ -77,6 +77,17 @@ return [
'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

Wyświetl plik

@ -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');
}
}

Wyświetl plik

@ -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');
}
}

Wyświetl plik

@ -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');
}
}

Wyświetl plik

@ -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');
}
}

Wyświetl plik

@ -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');
}
}

Wyświetl plik

@ -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');
}
}

Wyświetl plik

@ -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');
});
}
}

Wyświetl plik

@ -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');
}
}

Wyświetl plik

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="50px" height="50px" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>04/icon/color/svg/pixelfed-icon-color</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon-copy-8" fill-rule="nonzero">
<g id="photos" transform="translate(0.500000, 0.500000)">
<g id="Group-3">
<g id="Group-2">
<path d="M21.8377091,23.3816448 C21.8883023,23.7349539 21.9237176,24.0924792 21.9237176,24.4592798 C21.9237176,28.6332184 18.5086769,32.048259 14.3347384,32.048259 L7.58897916,32.048259 C3.41504062,32.048259 0,28.6332184 0,24.4592798 C0,20.6917733 2.78515535,17.5516224 6.39666621,16.9748599 C6.39666621,16.9748599 6.39750943,16.9757032 6.39750943,16.9757032 C6.39750943,16.9748599 6.39666621,16.9740167 6.39666621,16.9740167 C6.78623381,16.9116184 7.18254716,16.8703007 7.58897916,16.8703007 L14.3347384,16.8703007 C18.1418763,16.8703007 21.3090103,19.7144814 21.8377091,23.3816448 Z" id="Combined-Shape" fill="#BF3F8A"></path>
<path d="M31.9479158,6.39160689 C31.3660941,2.78515535 28.2284728,0.00505931944 24.4643391,0.00505931944 C20.7002055,0.00505931944 17.5625842,2.78515535 16.9807625,6.39160689 C17.3037157,6.62433559 17.6157071,6.87730156 17.9057747,7.16736921 L22.6758697,11.9374642 C23.5115007,12.7730951 24.1025978,13.7621921 24.4643391,14.8136873 C25.0191778,16.4242373 25.0191778,18.1831941 24.4643391,19.7937441 C24.2147461,20.5172268 23.8538479,21.2078239 23.3808016,21.8419252 C23.7357971,21.8933617 24.095852,21.9296201 24.4643391,21.9296201 C24.8328262,21.9296201 25.1928811,21.8942049 25.5478767,21.8419252 C26.331228,21.7280906 27.074948,21.4953619 27.7630154,21.1597603 C29.2943028,20.4135107 30.5380521,19.1697614 31.2843018,17.638474 C31.7716829,16.6384152 32.0533183,15.521992 32.0533183,14.3397977 L32.0533183,7.59403848 C32.0533183,7.1842336 32.0111573,6.78454737 31.9479158,6.39160689 Z" id="Shape" fill="#ED8611"></path>
<path d="M31.0229036,41.7503472 L26.2528086,36.9802522 C25.4171776,36.1446213 24.8260805,35.1555243 24.4643391,34.1040291 C23.9095005,32.4934791 23.9095005,30.7345224 24.4643391,29.1239724 C24.7139322,28.4004897 25.0748304,27.7098926 25.5478767,27.0757912 C25.1928811,27.0243548 24.8328262,26.9880963 24.4643391,26.9880963 C24.095852,26.9880963 23.7357971,27.0235116 23.3808016,27.0757912 C22.5974503,27.1896259 21.8537303,27.4223546 21.1656629,27.7579561 C19.6343755,28.5042057 18.3906262,29.7479551 17.6443765,31.2792424 C17.1569954,32.2793012 16.87536,33.3957244 16.87536,34.5779187 L16.87536,41.323678 C16.87536,41.7334828 16.917521,42.1331691 16.9807625,42.5261095 C17.5625842,46.1325611 20.7002055,48.9126571 24.4643391,48.9126571 C28.2284728,48.9126571 31.3660941,46.1325611 31.9479158,42.5261095 C31.6249626,42.2925376 31.3129712,42.0395717 31.0229036,41.7503472 Z" id="Shape" fill="#2E86DD"></path>
<path d="M42.5311689,16.9757032 C42.2984402,17.2986564 42.0454742,17.6106477 41.7554066,17.9007154 L36.9853115,22.6708104 C36.1496806,23.5064413 35.1605837,24.0966953 34.1090884,24.4592798 C32.4985384,25.0141185 30.7395817,25.0141185 29.1290317,24.4592798 C28.405549,24.2096867 27.7149519,23.8487886 27.0808505,23.3757422 C27.0294141,23.7307378 26.9939989,24.0907927 26.9939989,24.4592798 C26.9939989,24.8277669 27.0294141,25.1878218 27.0808505,25.5428174 C27.1946852,26.3261687 27.4274139,27.0698887 27.7630154,27.7579561 C28.509265,29.2892435 29.7530144,30.5329928 31.2843018,31.2792424 C32.2843606,31.7666235 33.4007837,32.048259 34.582978,32.048259 L41.3287373,32.048259 C41.7385422,32.048259 42.1382284,32.006098 42.5311689,31.9428565 C46.1376204,31.3610348 48.9177164,28.2234135 48.9177164,24.4592798 C48.9177164,20.6951462 46.1376204,17.5566817 42.5311689,16.9757032 Z" id="Shape" fill="#9EE85D"></path>
<path d="M41.7554066,7.16736921 C39.0942045,4.50616718 34.9093041,4.25320121 31.9479158,6.39160689 C32.0111573,6.78454737 32.0533183,7.1842336 32.0533183,7.59403848 L32.0533183,14.3397977 C32.0533183,15.521992 31.7716829,16.6384152 31.2843018,17.638474 C30.5380521,19.1697614 29.2943028,20.4135107 27.7630154,21.1597603 C27.074948,21.4953619 26.331228,21.7280906 25.5478767,21.8419252 C25.7628978,22.1294632 25.9922536,22.4094122 26.2528086,22.6699672 C26.5133635,22.9305221 26.7933125,23.159878 27.0808505,23.374899 C27.7149519,23.8479454 28.405549,24.2096867 29.1290317,24.4584366 C30.7395817,25.0132753 32.4985384,25.0132753 34.1090884,24.4584366 C35.1605837,24.095852 36.1496806,23.5055981 36.9853115,22.6699672 L41.7554066,17.8998722 C42.0454742,17.6098045 42.2984402,17.2978132 42.5311689,16.9748599 C44.6704178,14.0134716 44.4166086,9.82941445 41.7554066,7.16736921 Z" id="Shape" fill="#F0C51A"></path>
<path d="M42.5311689,31.9428565 C42.1382284,32.006098 41.7385422,32.048259 41.3287373,32.048259 L34.582978,32.048259 C33.4007837,32.048259 32.2843606,31.7666235 31.2843018,31.2792424 C29.7530144,30.5329928 28.509265,29.2892435 27.7630154,27.7579561 C27.4274139,27.0698887 27.1946852,26.3261687 27.0808505,25.5428174 C26.7933125,25.7578385 26.5133635,25.9871943 26.2528086,26.2477493 C25.9922536,26.5083042 25.7628978,26.7882532 25.5478767,27.0757912 C25.0748304,27.7098926 24.713089,28.4004897 24.4643391,29.1239724 C23.9095005,30.7345224 23.9095005,32.4934791 24.4643391,34.1040291 C24.8269237,35.1555243 25.4171776,36.1446213 26.2528086,36.9802522 L31.0229036,41.7503472 C31.3129712,42.0404149 31.6249626,42.2933809 31.9479158,42.5261095 C34.9093041,44.6645152 39.0942045,44.4115493 41.7554066,41.7503472 C44.4166086,39.0891452 44.6704178,34.9042448 42.5311689,31.9428565 Z" id="Shape" fill="#49B85F"></path>
<path d="M24.4643391,14.8145305 C24.1017546,13.7630353 23.5115007,12.7739384 22.6758697,11.9383074 L17.9057747,7.16736921 C17.6157071,6.87730156 17.3037157,6.62433559 16.9807625,6.39160689 C14.0193742,4.25320121 9.83447377,4.50616718 7.17327175,7.16736921 C4.51206972,9.82857123 4.25910375,14.0117852 6.39666621,16.9731735 C6.78623381,16.9116184 7.18254716,16.8703007 7.58897916,16.8703007 L14.3347384,16.8703007 C18.1418763,16.8703007 21.3090103,19.7144814 21.8377091,23.3816448 C21.841082,23.3791151 21.8444549,23.3774287 21.8478278,23.374899 C22.1353658,23.159878 22.4153148,22.9305221 22.6758697,22.6699672 C22.9364247,22.4094122 23.1657805,22.1294632 23.3808016,21.8419252 C23.8538479,21.2078239 24.2155893,20.5172268 24.4643391,19.7937441 C25.0191778,18.1831941 25.0191778,16.4242373 24.4643391,14.8145305 Z" id="Shape" fill="#ED5B47"></path>
<path d="M22.6758697,26.2477493 C22.4153148,25.9871943 22.1353658,25.7578385 21.8478278,25.5428174 C21.2137264,25.069771 20.5231293,24.7080297 19.7996466,24.4592798 C18.1890966,23.9044411 16.4301399,23.9044411 14.8195899,24.4592798 C13.7680946,24.8218644 12.7789977,25.4121183 11.9433668,26.2477493 L7.17327175,31.0178443 C6.8832041,31.3079119 6.63023813,31.6199033 6.39750943,31.9428565 C4.25910375,34.9042448 4.51206972,39.0891452 7.17327175,41.7503472 C9.83447377,44.4115493 14.0193742,44.6645152 16.9807625,42.5261095 C16.917521,42.1331691 16.87536,41.7334828 16.87536,41.323678 L16.87536,34.5779187 C16.87536,33.3957244 17.1569954,32.2793012 17.6443765,31.2792424 C18.3906262,29.7479551 19.6343755,28.5042057 21.1656629,27.7579561 C21.8537303,27.4223546 22.5974503,27.1896259 23.3808016,27.0757912 C23.1657805,26.7882532 22.9364247,26.5083042 22.6758697,26.2477493 Z" id="Shape" fill="#8D59A8"></path>
</g>
<path d="M22.9797433,30.3617318 L27.2641103,30.3617318 C31.3001553,30.3617318 34.5720162,27.1514896 34.5720162,23.191455 C34.5720162,19.2314205 31.3001553,16.0211782 27.2641103,16.0211782 L21.0804977,16.0211782 C18.7520102,16.0211782 16.8643981,17.8732411 16.8643981,20.1578764 L16.8643981,36.258456 L22.9797433,30.3617318 Z" id="Path-6-Copy-2" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 8.1 KiB

Wyświetl plik

@ -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>

Wyświetl plik

@ -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">&times;</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">&times;</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>

Wyświetl plik

@ -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">&times;</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">&times;</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>

Wyświetl plik

@ -1,8 +1,8 @@
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
<div class="container">
<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">
<span class="h4 font-weight-bold mb-0">{{ config('app.name', 'Laravel') }}</span>
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2">
<span class="font-weight-bold mb-0" style="font-size:20px;">{{ config('app.name', 'Laravel') }}</span>
</a>
<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>
</li>
<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 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">

Wyświetl plik

@ -35,49 +35,6 @@
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address" value="{{Auth::user()->email}}" readonly>
</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>
<div class="form-group row">
<div class="col-sm-9">