mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-10 00:34:50 +00:00
Merge branch 'frontend-ui-refactor' into 598-avatar-sizing
This commit is contained in:
commit
e5fe7eb8a9
100 changed files with 5684 additions and 5930 deletions
|
@ -18,7 +18,7 @@ testing and development.
|
|||
|
||||
## Requirements
|
||||
- PHP >= 7.1.3 < 7.3 (7.2.x recommended for stable version)
|
||||
- MySQL >= 5.7, Postgres (MariaDB and sqlite are not supported yet)
|
||||
- MySQL >= 5.7 (Postgres, MariaDB and sqlite are not supported)
|
||||
- Redis
|
||||
- Composer
|
||||
- GD or ImageMagick
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Console\Commands;
|
|||
use Illuminate\Console\Command;
|
||||
use App\{Profile, User};
|
||||
use DB;
|
||||
use App\Util\Lexer\RestrictedNames;
|
||||
|
||||
class FixUsernames extends Command
|
||||
{
|
||||
|
@ -43,8 +44,13 @@ class FixUsernames extends Command
|
|||
|
||||
$affected = collect([]);
|
||||
|
||||
$users = User::chunk(100, function($users) use($affected) {
|
||||
$restricted = RestrictedNames::get();
|
||||
|
||||
$users = User::chunk(100, function($users) use($affected, $restricted) {
|
||||
foreach($users as $user) {
|
||||
if(in_array($user->username, $restricted)) {
|
||||
$affected->push($user);
|
||||
}
|
||||
$val = str_replace(['-', '_'], '', $user->username);
|
||||
if(!ctype_alnum($val)) {
|
||||
$this->info('Found invalid username: ' . $user->username);
|
||||
|
@ -58,11 +64,13 @@ class FixUsernames extends Command
|
|||
$opts = [
|
||||
'Random replace (assigns random username)',
|
||||
'Best try replace (assigns alpha numeric username)',
|
||||
'Manual replace (manually set username)'
|
||||
'Manual replace (manually set username)',
|
||||
'Skip (do not replace. Use at your own risk)'
|
||||
];
|
||||
|
||||
foreach($affected as $u) {
|
||||
$old = $u->username;
|
||||
$this->info("Found user: {$old}");
|
||||
$opt = $this->choice('Select fix method:', $opts, 0);
|
||||
|
||||
switch ($opt) {
|
||||
|
@ -83,23 +91,31 @@ class FixUsernames extends Command
|
|||
$new = $this->ask('Enter new username:');
|
||||
$this->info('New username: ' . $new);
|
||||
break;
|
||||
|
||||
case $opts[3]:
|
||||
$new = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
$new = "user_" . str_random(6);
|
||||
break;
|
||||
}
|
||||
|
||||
DB::transaction(function() use($u, $new) {
|
||||
$profile = $u->profile;
|
||||
$profile->username = $new;
|
||||
$u->username = $new;
|
||||
$u->save();
|
||||
$profile->save();
|
||||
});
|
||||
if($new) {
|
||||
DB::transaction(function() use($u, $new) {
|
||||
$profile = $u->profile;
|
||||
$profile->username = $new;
|
||||
$u->username = $new;
|
||||
$u->save();
|
||||
$profile->save();
|
||||
});
|
||||
}
|
||||
$this->info('Selected: ' . $opt);
|
||||
}
|
||||
|
||||
$this->info('Fixed ' . $affected->count() . ' usernames!');
|
||||
} else {
|
||||
$this->info('No affected usernames found!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,4 +17,14 @@ class FollowRequest extends Model
|
|||
{
|
||||
return $this->belongsTo(Profile::class, 'following_id', 'id');
|
||||
}
|
||||
|
||||
public function actor()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'follower_id', 'id');
|
||||
}
|
||||
|
||||
public function target()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'following_id', 'id');
|
||||
}
|
||||
}
|
||||
|
|
101
app/Http/Controllers/Admin/AdminInstanceController.php
Normal file
101
app/Http/Controllers/Admin/AdminInstanceController.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{Instance, Profile};
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
trait AdminInstanceController
|
||||
{
|
||||
|
||||
public function instances(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'filter' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'min:1',
|
||||
'max:20',
|
||||
Rule::in(['autocw', 'unlisted', 'banned'])
|
||||
],
|
||||
]);
|
||||
if($request->has('filter') && $request->filled('filter')) {
|
||||
switch ($request->filter) {
|
||||
case 'autocw':
|
||||
$instances = Instance::whereAutoCw(true)->orderByDesc('id')->paginate(5);
|
||||
break;
|
||||
case 'unlisted':
|
||||
$instances = Instance::whereUnlisted(true)->orderByDesc('id')->paginate(5);
|
||||
break;
|
||||
case 'banned':
|
||||
$instances = Instance::whereBanned(true)->orderByDesc('id')->paginate(5);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$instances = Instance::orderByDesc('id')->paginate(5);
|
||||
}
|
||||
return view('admin.instances.home', compact('instances'));
|
||||
}
|
||||
|
||||
public function instanceScan(Request $request)
|
||||
{
|
||||
DB::transaction(function() {
|
||||
Profile::whereNotNull('domain')
|
||||
->groupBy('domain')
|
||||
->chunk(50, function($domains) {
|
||||
foreach($domains as $domain) {
|
||||
Instance::firstOrCreate([
|
||||
'domain' => $domain->domain
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function instanceShow(Request $request, $id)
|
||||
{
|
||||
$instance = Instance::findOrFail($id);
|
||||
return view('admin.instances.show', compact('instance'));
|
||||
}
|
||||
|
||||
public function instanceEdit(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'action' => [
|
||||
'required',
|
||||
'string',
|
||||
'min:1',
|
||||
'max:20',
|
||||
Rule::in(['autocw', 'unlist', 'ban'])
|
||||
],
|
||||
]);
|
||||
|
||||
$instance = Instance::findOrFail($id);
|
||||
$unlisted = $instance->unlisted;
|
||||
$autocw = $instance->auto_cw;
|
||||
$banned = $instance->banned;
|
||||
|
||||
switch ($request->action) {
|
||||
case 'autocw':
|
||||
$instance->auto_cw = $autocw == true ? false : true;
|
||||
$instance->save();
|
||||
break;
|
||||
|
||||
case 'unlist':
|
||||
$instance->unlisted = $unlisted == true ? false : true;
|
||||
$instance->save();
|
||||
break;
|
||||
|
||||
case 'ban':
|
||||
$instance->banned = $banned == true ? false : true;
|
||||
$instance->save();
|
||||
break;
|
||||
}
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
48
app/Http/Controllers/Admin/AdminMediaController.php
Normal file
48
app/Http/Controllers/Admin/AdminMediaController.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{
|
||||
Media,
|
||||
Profile,
|
||||
Status
|
||||
};
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
trait AdminMediaController
|
||||
{
|
||||
public function media(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'layout' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'min:1',
|
||||
'max:4',
|
||||
Rule::in(['grid','list'])
|
||||
],
|
||||
'search' => 'nullable|string|min:1|max:20'
|
||||
]);
|
||||
if($request->filled('search')) {
|
||||
$profiles = Profile::where('username', 'like', '%'.$request->input('search').'%')->pluck('id')->toArray();
|
||||
$media = Media::whereHas('status')
|
||||
->with('status')
|
||||
->orderby('id', 'desc')
|
||||
->whereIn('profile_id', $profiles)
|
||||
->orWhere('mime', $request->input('search'))
|
||||
->paginate(12);
|
||||
} else {
|
||||
$media = Media::whereHas('status')->with('status')->orderby('id', 'desc')->paginate(12);
|
||||
}
|
||||
return view('admin.media.home', compact('media'));
|
||||
}
|
||||
|
||||
public function mediaShow(Request $request, $id)
|
||||
{
|
||||
$media = Media::findOrFail($id);
|
||||
return view('admin.media.show', compact('media'));
|
||||
}
|
||||
}
|
|
@ -13,7 +13,8 @@ use App\{
|
|||
Avatar,
|
||||
Notification,
|
||||
Media,
|
||||
Profile
|
||||
Profile,
|
||||
Status
|
||||
};
|
||||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
|
@ -23,6 +24,7 @@ use App\Transformer\Api\{
|
|||
};
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use App\Jobs\AvatarPipeline\AvatarOptimize;
|
||||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||
use App\Jobs\VideoPipeline\{
|
||||
|
@ -97,18 +99,52 @@ class BaseApiController extends Controller
|
|||
|
||||
public function accountStatuses(Request $request, $id)
|
||||
{
|
||||
$pid = Auth::user()->profile->id;
|
||||
$profile = Profile::findOrFail($id);
|
||||
$statuses = $profile->statuses();
|
||||
if($pid === $profile->id) {
|
||||
$statuses = $statuses->orderBy('id', 'desc')->paginate(20);
|
||||
$this->validate($request, [
|
||||
'only_media' => 'nullable',
|
||||
'pinned' => 'nullable',
|
||||
'exclude_replies' => 'nullable',
|
||||
'max_id' => 'nullable|integer|min:1',
|
||||
'since_id' => 'nullable|integer|min:1',
|
||||
'min_id' => 'nullable|integer|min:1',
|
||||
'limit' => 'nullable|integer|min:1|max:24'
|
||||
]);
|
||||
$limit = $request->limit ?? 20;
|
||||
$max_id = $request->max_id ?? false;
|
||||
$min_id = $request->min_id ?? false;
|
||||
$since_id = $request->since_id ?? false;
|
||||
$only_media = $request->only_media ?? false;
|
||||
$user = Auth::user();
|
||||
$account = Profile::findOrFail($id);
|
||||
$statuses = $account->statuses()->getQuery();
|
||||
if($only_media == true) {
|
||||
$statuses = $statuses
|
||||
->whereHas('media')
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id');
|
||||
}
|
||||
if($id == $account->id && !$max_id && !$min_id && !$since_id) {
|
||||
$statuses = $statuses->orderBy('id', 'desc')
|
||||
->paginate($limit);
|
||||
} else if($since_id) {
|
||||
$statuses = $statuses->where('id', '>', $since_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->paginate($limit);
|
||||
} else if($min_id) {
|
||||
$statuses = $statuses->where('id', '>', $min_id)
|
||||
->orderBy('id', 'ASC')
|
||||
->paginate($limit);
|
||||
} else if($max_id) {
|
||||
$statuses = $statuses->where('id', '<', $max_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->paginate($limit);
|
||||
} else {
|
||||
$statuses = $statuses->whereVisibility('public')->orderBy('id', 'desc')->paginate(20);
|
||||
$statuses = $statuses->whereVisibility('public')->orderBy('id', 'desc')->paginate($limit);
|
||||
}
|
||||
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
|
||||
//$resource->setPaginator(new IlluminatePaginatorAdapter($statuses));
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res);
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function followSuggestions(Request $request)
|
||||
|
@ -265,4 +301,13 @@ class BaseApiController extends Controller
|
|||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function showAccount(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::whereNull('domain')->whereNull('status')->findOrFail($id);
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
|
|
66
app/Http/Controllers/Api/InstanceApiController.php
Normal file
66
app/Http/Controllers/Api/InstanceApiController.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\{Profile, Status, User};
|
||||
use Cache;
|
||||
|
||||
class InstanceApiController extends Controller {
|
||||
|
||||
protected function getData()
|
||||
{
|
||||
$contact = Cache::remember('api:v1:instance:contact', 1440, function() {
|
||||
$admin = User::whereIsAdmin(true)->first()->profile;
|
||||
return [
|
||||
'id' => $admin->id,
|
||||
'username' => $admin->username,
|
||||
'acct' => $admin->username,
|
||||
'display_name' => e($admin->name),
|
||||
'locked' => (bool) $admin->is_private,
|
||||
'bot' => false,
|
||||
'created_at' => $admin->created_at->format('c'),
|
||||
'note' => e($admin->bio),
|
||||
'url' => $admin->url(),
|
||||
'avatar' => $admin->avatarUrl(),
|
||||
'avatar_static' => $admin->avatarUrl(),
|
||||
'header' => null,
|
||||
'header_static' => null,
|
||||
'moved' => null,
|
||||
'fields' => null,
|
||||
'bot' => null,
|
||||
];
|
||||
});
|
||||
|
||||
$res = [
|
||||
'uri' => config('pixelfed.domain.app'),
|
||||
'title' => config('app.name'),
|
||||
'description' => '',
|
||||
'version' => config('pixelfed.version'),
|
||||
'urls' => [],
|
||||
'stats' => [
|
||||
'user_count' => User::count(),
|
||||
'status_count' => Status::whereNull('uri')->count(),
|
||||
'domain_count' => Profile::whereNotNull('domain')
|
||||
->groupBy('domain')
|
||||
->pluck('domain')
|
||||
->count()
|
||||
],
|
||||
'thumbnail' => '',
|
||||
'languages' => [],
|
||||
'contact_account' => $contact
|
||||
];
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function instance()
|
||||
{
|
||||
$res = Cache::remember('api:v1:instance', 60, function() {
|
||||
return json_encode($this->getData());
|
||||
});
|
||||
|
||||
return response($res)->header('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
}
|
|
@ -2,13 +2,18 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Auth;
|
||||
|
||||
use App\Comment;
|
||||
use App\Jobs\CommentPipeline\CommentPipeline;
|
||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use App\Profile;
|
||||
use App\Status;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use League\Fractal;
|
||||
use App\Transformer\Api\StatusTransformer;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
|
@ -57,7 +62,19 @@ class CommentController extends Controller
|
|||
CommentPipeline::dispatch($status, $reply);
|
||||
|
||||
if ($request->ajax()) {
|
||||
$response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url(), 'comment' => $reply->caption];
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$entity = new Fractal\Resource\Item($reply, new StatusTransformer());
|
||||
$entity = $fractal->createData($entity)->toArray();
|
||||
$response = [
|
||||
'code' => 200,
|
||||
'msg' => 'Comment saved',
|
||||
'username' => $profile->username,
|
||||
'url' => $reply->url(),
|
||||
'profile' => $profile->url(),
|
||||
'comment' => $reply->caption,
|
||||
'entity' => $entity,
|
||||
];
|
||||
} else {
|
||||
$response = redirect($status->url());
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use Illuminate\Http\Request;
|
|||
use League\Fractal;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use \Zttp\Zttp;
|
||||
|
||||
class FederationController extends Controller
|
||||
{
|
||||
|
@ -81,37 +82,38 @@ class FederationController extends Controller
|
|||
{
|
||||
$res = Cache::remember('api:nodeinfo', 60, function () {
|
||||
return [
|
||||
'metadata' => [
|
||||
'nodeName' => config('app.name'),
|
||||
'software' => [
|
||||
'homepage' => 'https://pixelfed.org',
|
||||
'github' => 'https://github.com/pixelfed',
|
||||
'follow' => 'https://mastodon.social/@pixelfed',
|
||||
],
|
||||
],
|
||||
'openRegistrations' => config('pixelfed.open_registration'),
|
||||
'protocols' => [
|
||||
'activitypub',
|
||||
],
|
||||
'services' => [
|
||||
'inbound' => [],
|
||||
'outbound' => [],
|
||||
],
|
||||
'software' => [
|
||||
'name' => 'pixelfed',
|
||||
'version' => config('pixelfed.version'),
|
||||
],
|
||||
'usage' => [
|
||||
'localPosts' => \App\Status::whereLocal(true)->whereHas('media')->count(),
|
||||
'localComments' => \App\Status::whereLocal(true)->whereNotNull('in_reply_to_id')->count(),
|
||||
'users' => [
|
||||
'total' => \App\User::count(),
|
||||
'activeHalfyear' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(6)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
'activeMonth' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(1)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
],
|
||||
],
|
||||
'version' => '2.0',
|
||||
];
|
||||
'metadata' => [
|
||||
'nodeName' => config('app.name'),
|
||||
'software' => [
|
||||
'homepage' => 'https://pixelfed.org',
|
||||
'github' => 'https://github.com/pixelfed',
|
||||
'follow' => 'https://mastodon.social/@pixelfed',
|
||||
],
|
||||
'captcha' => (bool) config('pixelfed.recaptcha'),
|
||||
],
|
||||
'openRegistrations' => config('pixelfed.open_registration'),
|
||||
'protocols' => [
|
||||
'activitypub',
|
||||
],
|
||||
'services' => [
|
||||
'inbound' => [],
|
||||
'outbound' => [],
|
||||
],
|
||||
'software' => [
|
||||
'name' => 'pixelfed',
|
||||
'version' => config('pixelfed.version'),
|
||||
],
|
||||
'usage' => [
|
||||
'localPosts' => \App\Status::whereLocal(true)->whereHas('media')->count(),
|
||||
'localComments' => \App\Status::whereLocal(true)->whereNotNull('in_reply_to_id')->count(),
|
||||
'users' => [
|
||||
'total' => \App\User::count(),
|
||||
'activeHalfyear' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(6)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
'activeMonth' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(1)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
],
|
||||
],
|
||||
'version' => '2.0',
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($res, 200, [
|
||||
|
@ -238,7 +240,7 @@ XML;
|
|||
}
|
||||
$signatureData = HttpSignature::parseSignatureHeader($signature);
|
||||
$keyId = Helpers::validateUrl($signatureData['keyId']);
|
||||
$actor = Profile::whereKeyId($keyId)->first();
|
||||
$actor = Profile::whereKeyId($keyId)->whereNotNull('remote_url')->firstOrFail();
|
||||
$res = Zttp::timeout(5)->withHeaders([
|
||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org',
|
||||
|
|
|
@ -39,6 +39,7 @@ class FollowerController extends Controller
|
|||
$user = Auth::user()->profile;
|
||||
$target = Profile::where('id', '!=', $user->id)->whereNull('status')->findOrFail($item);
|
||||
$private = (bool) $target->is_private;
|
||||
$remote = (bool) $target->domain;
|
||||
$blocked = UserFilter::whereUserId($target->id)
|
||||
->whereFilterType('block')
|
||||
->whereFilterableId($user->id)
|
||||
|
@ -51,7 +52,7 @@ class FollowerController extends Controller
|
|||
|
||||
$isFollowing = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->count();
|
||||
|
||||
if($private == true && $isFollowing == 0) {
|
||||
if($private == true && $isFollowing == 0 || $remote == true) {
|
||||
$follow = FollowRequest::firstOrCreate([
|
||||
'follower_id' => $user->id,
|
||||
'following_id' => $target->id
|
||||
|
|
|
@ -54,6 +54,7 @@ class InternalApiController extends Controller
|
|||
$attachments = [];
|
||||
$status = new Status;
|
||||
$mimes = [];
|
||||
$cw = false;
|
||||
|
||||
foreach($medias as $k => $media) {
|
||||
$m = Media::findOrFail($media['id']);
|
||||
|
@ -64,7 +65,8 @@ class InternalApiController extends Controller
|
|||
$m->license = $media['license'];
|
||||
$m->caption = strip_tags($media['alt']);
|
||||
$m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k;
|
||||
if($media['cw'] == true) {
|
||||
if($media['cw'] == true || $profile->cw == true) {
|
||||
$cw = true;
|
||||
$m->is_nsfw = true;
|
||||
$status->is_nsfw = true;
|
||||
}
|
||||
|
@ -84,6 +86,9 @@ class InternalApiController extends Controller
|
|||
$media->save();
|
||||
}
|
||||
|
||||
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
|
||||
$cw = $profile->cw == true ? true : $cw;
|
||||
$status->is_nsfw = $cw;
|
||||
$status->visibility = $visibility;
|
||||
$status->scope = $visibility;
|
||||
$status->type = StatusController::mimeTypeCheck($mimes);
|
||||
|
|
|
@ -187,7 +187,7 @@ class ProfileController extends Controller
|
|||
return view('profile.private', compact('user', 'is_following'));
|
||||
}
|
||||
}
|
||||
$followers = $profile->followers()->whereNull('status')->orderBy('created_at', 'desc')->simplePaginate(12);
|
||||
$followers = $profile->followers()->whereNull('status')->orderBy('followers.created_at', 'desc')->simplePaginate(12);
|
||||
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
|
||||
if ($user->remote_url) {
|
||||
$settings = new \StdClass;
|
||||
|
@ -217,7 +217,7 @@ class ProfileController extends Controller
|
|||
return view('profile.private', compact('user', 'is_following'));
|
||||
}
|
||||
}
|
||||
$following = $profile->following()->whereNull('status')->orderBy('created_at', 'desc')->simplePaginate(12);
|
||||
$following = $profile->following()->whereNull('status')->orderBy('followers.created_at', 'desc')->simplePaginate(12);
|
||||
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
|
||||
if ($user->remote_url) {
|
||||
$settings = new \StdClass;
|
||||
|
|
|
@ -233,6 +233,8 @@ class PublicApiController extends Controller
|
|||
$dir = $min ? '>' : '<';
|
||||
$id = $min ?? $max;
|
||||
$timeline = Status::whereHas('media')
|
||||
->whereLocal(true)
|
||||
->whereNull('uri')
|
||||
->where('id', $dir, $id)
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
->whereNull('in_reply_to_id')
|
||||
|
@ -244,6 +246,8 @@ class PublicApiController extends Controller
|
|||
->get();
|
||||
} else {
|
||||
$timeline = Status::whereHas('media')
|
||||
->whereLocal(true)
|
||||
->whereNull('uri')
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id')
|
||||
|
@ -295,6 +299,8 @@ class PublicApiController extends Controller
|
|||
$dir = $min ? '>' : '<';
|
||||
$id = $min ?? $max;
|
||||
$timeline = Status::whereHas('media')
|
||||
->whereLocal(true)
|
||||
->whereNull('uri')
|
||||
->where('id', $dir, $id)
|
||||
->whereIn('profile_id', $following)
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
|
@ -307,6 +313,8 @@ class PublicApiController extends Controller
|
|||
->get();
|
||||
} else {
|
||||
$timeline = Status::whereHas('media')
|
||||
->whereLocal(true)
|
||||
->whereNull('uri')
|
||||
->whereIn('profile_id', $following)
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
->whereNull('in_reply_to_id')
|
||||
|
|
|
@ -149,11 +149,17 @@ class SettingsController extends Controller
|
|||
|
||||
public function removeAccountPermanent(Request $request)
|
||||
{
|
||||
if(config('pixelfed.account_deletion') == false) {
|
||||
abort(404);
|
||||
}
|
||||
return view('settings.remove.permanent');
|
||||
}
|
||||
|
||||
public function removeAccountPermanentSubmit(Request $request)
|
||||
{
|
||||
if(config('pixelfed.account_deletion') == false) {
|
||||
abort(404);
|
||||
}
|
||||
$user = Auth::user();
|
||||
if($user->is_admin == true) {
|
||||
return abort(400, 'You cannot delete an admin account.');
|
||||
|
|
|
@ -5,10 +5,12 @@ namespace App\Http\Controllers;
|
|||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use App\Jobs\StatusPipeline\StatusDelete;
|
||||
use App\Jobs\SharePipeline\SharePipeline;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\Status;
|
||||
use App\Transformer\ActivityPub\StatusTransformer;
|
||||
use App\Transformer\ActivityPub\Verb\Note;
|
||||
use App\User;
|
||||
use Auth;
|
||||
use Cache;
|
||||
|
@ -19,7 +21,7 @@ class StatusController extends Controller
|
|||
{
|
||||
public function show(Request $request, $username, int $id)
|
||||
{
|
||||
$user = Profile::whereUsername($username)->firstOrFail();
|
||||
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||
|
||||
if($user->status != null) {
|
||||
return ProfileController::accountCheck($user);
|
||||
|
@ -55,6 +57,39 @@ class StatusController extends Controller
|
|||
return view($template, compact('user', 'status'));
|
||||
}
|
||||
|
||||
public function showObject(Request $request, $username, int $id)
|
||||
{
|
||||
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||
|
||||
if($user->status != null) {
|
||||
return ProfileController::accountCheck($user);
|
||||
}
|
||||
|
||||
$status = Status::whereProfileId($user->id)
|
||||
->whereNotIn('visibility',['draft','direct'])
|
||||
->findOrFail($id);
|
||||
|
||||
if($status->uri) {
|
||||
$url = $status->uri;
|
||||
if(ends_with($url, '/activity')) {
|
||||
$url = str_replace('/activity', '', $url);
|
||||
}
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
if($status->visibility == 'private' || $user->is_private) {
|
||||
if(!Auth::check()) {
|
||||
abort(403);
|
||||
}
|
||||
$pid = Auth::user()->profile;
|
||||
if($user->followedBy($pid) == false && $user->id !== $pid->id) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->showActivityPub($request, $status);
|
||||
}
|
||||
|
||||
public function compose()
|
||||
{
|
||||
$this->authCheck();
|
||||
|
@ -91,6 +126,9 @@ class StatusController extends Controller
|
|||
$profile = $user->profile;
|
||||
$visibility = $this->validateVisibility($request->visibility);
|
||||
|
||||
$cw = $profile->cw == true ? true : $cw;
|
||||
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
|
||||
|
||||
$status = new Status();
|
||||
$status->profile_id = $profile->id;
|
||||
$status->caption = strip_tags($request->caption);
|
||||
|
@ -197,8 +235,10 @@ class StatusController extends Controller
|
|||
$share = new Status();
|
||||
$share->profile_id = $profile->id;
|
||||
$share->reblog_of_id = $status->id;
|
||||
$share->in_reply_to_profile_id = $status->profile_id;
|
||||
$share->save();
|
||||
$count++;
|
||||
SharePipeline::dispatch($share);
|
||||
}
|
||||
|
||||
if ($request->ajax()) {
|
||||
|
@ -213,7 +253,7 @@ class StatusController extends Controller
|
|||
public function showActivityPub(Request $request, $status)
|
||||
{
|
||||
$fractal = new Fractal\Manager();
|
||||
$resource = new Fractal\Resource\Item($status, new StatusTransformer());
|
||||
$resource = new Fractal\Resource\Item($status, new Note());
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
|
||||
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
||||
|
|
|
@ -6,5 +6,63 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class Instance extends Model
|
||||
{
|
||||
//
|
||||
protected $fillable = ['domain'];
|
||||
|
||||
public function profiles()
|
||||
{
|
||||
return $this->hasMany(Profile::class, 'domain', 'domain');
|
||||
}
|
||||
|
||||
public function statuses()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Status::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function reported()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Report::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'reported_profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function reports()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Report::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function media()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Media::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
return url("/i/admin/instances/show/{$this->id}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ class AvatarOptimize implements ShouldQueue
|
|||
protected $profile;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -20,6 +20,13 @@ class CreateAvatar implements ShouldQueue
|
|||
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -20,6 +20,13 @@ class CommentPipeline implements ShouldQueue
|
|||
protected $status;
|
||||
protected $comment;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
64
app/Jobs/FollowPipeline/FollowActivityPubDeliver.php
Normal file
64
app/Jobs/FollowPipeline/FollowActivityPubDeliver.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\FollowPipeline;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
use Cache, Log, Redis;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use App\FollowRequest;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Transformer\ActivityPub\Verb\Follow;
|
||||
|
||||
class FollowActivityPubDeliver implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $followRequest;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(FollowRequest $followRequest)
|
||||
{
|
||||
$this->followRequest = $followRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$follow = $this->followRequest;
|
||||
$actor = $follow->actor;
|
||||
$target = $follow->target;
|
||||
|
||||
if($target->domain == null || $target->inbox_url == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($follow, new Follow());
|
||||
$activity = $fractal->createData($resource)->toArray();
|
||||
$url = $target->sharedInbox ?? $target->inbox_url;
|
||||
|
||||
Helpers::sendSignedObject($actor, $url, $activity);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,13 @@ class FollowPipeline implements ShouldQueue
|
|||
|
||||
protected $follower;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -15,6 +15,13 @@ class ImageOptimize implements ShouldQueue
|
|||
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -16,6 +16,13 @@ class ImageResize implements ShouldQueue
|
|||
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -17,6 +17,13 @@ class ImageThumbnail implements ShouldQueue
|
|||
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Jobs\ImageOptimizePipeline;
|
||||
|
||||
use Storage;
|
||||
use App\Media;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
@ -9,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use ImageOptimizer;
|
||||
use Illuminate\Http\File;
|
||||
|
||||
class ImageUpdate implements ShouldQueue
|
||||
{
|
||||
|
@ -17,11 +19,17 @@ class ImageUpdate implements ShouldQueue
|
|||
protected $media;
|
||||
|
||||
protected $protectedMimes = [
|
||||
'image/gif',
|
||||
'image/bmp',
|
||||
'video/mp4',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
];
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
@ -43,21 +51,31 @@ class ImageUpdate implements ShouldQueue
|
|||
$path = storage_path('app/'.$media->media_path);
|
||||
$thumb = storage_path('app/'.$media->thumbnail_path);
|
||||
|
||||
try {
|
||||
if (!in_array($media->mime, $this->protectedMimes)) {
|
||||
ImageOptimizer::optimize($thumb);
|
||||
ImageOptimizer::optimize($path);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
if (in_array($media->mime, $this->protectedMimes) == true) {
|
||||
ImageOptimizer::optimize($thumb);
|
||||
ImageOptimizer::optimize($path);
|
||||
}
|
||||
|
||||
if (!is_file($path) || !is_file($thumb)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$photo_size = filesize($path);
|
||||
$thumb_size = filesize($thumb);
|
||||
$total = ($photo_size + $thumb_size);
|
||||
$media->size = $total;
|
||||
$media->save();
|
||||
|
||||
if(config('pixelfed.cloud_storage') == true) {
|
||||
$p = explode('/', $media->media_path);
|
||||
$monthHash = $p[2];
|
||||
$userHash = $p[3];
|
||||
$storagePath = "public/m/{$monthHash}/{$userHash}";
|
||||
$file = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($path), 'public');
|
||||
$url = Storage::disk(config('filesystems.cloud'))->url($file);
|
||||
$media->cdn_url = $url;
|
||||
$media->optimized_url = $url;
|
||||
$media->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,14 @@ class ImportInstagram implements ShouldQueue
|
|||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $job;
|
||||
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -19,6 +19,13 @@ class LikePipeline implements ShouldQueue
|
|||
|
||||
protected $like;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -18,6 +18,13 @@ class MentionPipeline implements ShouldQueue
|
|||
protected $status;
|
||||
protected $mention;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -17,7 +17,14 @@ class SharePipeline implements ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $like;
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
|
@ -37,32 +44,32 @@ class SharePipeline implements ShouldQueue
|
|||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$actor = $this->status->profile;
|
||||
$target = $this->status->parent()->profile;
|
||||
$actor = $status->profile;
|
||||
$target = $status->parent()->profile;
|
||||
|
||||
if ($status->url !== null) {
|
||||
if ($status->uri !== null) {
|
||||
// Ignore notifications to remote statuses
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = Notification::whereProfileId($status->profile_id)
|
||||
->whereActorId($actor->id)
|
||||
->whereAction('like')
|
||||
->whereItemId($status->id)
|
||||
$exists = Notification::whereProfileId($target->id)
|
||||
->whereActorId($status->profile_id)
|
||||
->whereAction('share')
|
||||
->whereItemId($status->reblog_of_id)
|
||||
->whereItemType('App\Status')
|
||||
->count();
|
||||
|
||||
if ($actor->id === $status->profile_id || $exists !== 0) {
|
||||
if ($target->id === $status->profile_id || $exists !== 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$notification = new Notification();
|
||||
$notification->profile_id = $status->profile_id;
|
||||
$notification = new Notification;
|
||||
$notification->profile_id = $target->id;
|
||||
$notification->actor_id = $actor->id;
|
||||
$notification->action = 'like';
|
||||
$notification->message = $like->toText();
|
||||
$notification->rendered = $like->toHtml();
|
||||
$notification->action = 'share';
|
||||
$notification->message = $status->shareToText();
|
||||
$notification->rendered = $status->shareToHtml();
|
||||
$notification->item_id = $status->id;
|
||||
$notification->item_type = "App\Status";
|
||||
$notification->save();
|
||||
|
|
|
@ -16,7 +16,14 @@ class NewStatusPipeline implements ShouldQueue
|
|||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
@ -37,7 +44,10 @@ class NewStatusPipeline implements ShouldQueue
|
|||
$status = $this->status;
|
||||
|
||||
StatusEntityLexer::dispatch($status);
|
||||
StatusActivityPubDeliver::dispatch($status);
|
||||
|
||||
if(config('pixelfed.activitypub_enabled') == true) {
|
||||
StatusActivityPubDeliver::dispatch($status);
|
||||
}
|
||||
|
||||
// Cache::forever('post.'.$status->id, $status);
|
||||
// $redis = Redis::connection();
|
||||
|
|
|
@ -18,7 +18,14 @@ class StatusActivityPubDeliver implements ShouldQueue
|
|||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -19,7 +19,14 @@ class StatusDelete implements ShouldQueue
|
|||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -24,7 +24,14 @@ class StatusEntityLexer implements ShouldQueue
|
|||
protected $status;
|
||||
protected $entities;
|
||||
protected $autolink;
|
||||
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
@ -42,7 +49,10 @@ class StatusEntityLexer implements ShouldQueue
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->parseEntities();
|
||||
$profile = $this->status->profile;
|
||||
if($profile->no_autolink == false) {
|
||||
$this->parseEntities();
|
||||
}
|
||||
}
|
||||
|
||||
public function parseEntities()
|
||||
|
|
|
@ -38,32 +38,11 @@ class AuthLogin
|
|||
});
|
||||
}
|
||||
|
||||
if(empty($user->profile)) {
|
||||
DB::transaction(function() use($user) {
|
||||
$profile = new Profile();
|
||||
$profile->user_id = $user->id;
|
||||
$profile->username = $user->username;
|
||||
$profile->name = $user->name;
|
||||
$pkiConfig = [
|
||||
'digest_alg' => 'sha512',
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
$pki = openssl_pkey_new($pkiConfig);
|
||||
openssl_pkey_export($pki, $pki_private);
|
||||
$pki_public = openssl_pkey_get_details($pki);
|
||||
$pki_public = $pki_public['key'];
|
||||
|
||||
$profile->private_key = $pki_private;
|
||||
$profile->public_key = $pki_public;
|
||||
$profile->save();
|
||||
|
||||
CreateAvatar::dispatch($profile);
|
||||
});
|
||||
}
|
||||
|
||||
if($user->status != null) {
|
||||
$profile = $user->profile;
|
||||
if(!$profile) {
|
||||
return;
|
||||
}
|
||||
switch ($user->status) {
|
||||
case 'disabled':
|
||||
$profile->status = null;
|
||||
|
|
|
@ -17,13 +17,23 @@ class Media extends Model
|
|||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(Status::class);
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
if(!empty($this->remote_media) && $this->remote_url) {
|
||||
$url = $this->remote_url;
|
||||
} else {
|
||||
$path = $this->media_path;
|
||||
$url = Storage::url($path);
|
||||
$url = $this->cdn_url ?? Storage::url($path);
|
||||
}
|
||||
|
||||
return url($url);
|
||||
|
@ -37,6 +47,11 @@ class Media extends Model
|
|||
return url($url);
|
||||
}
|
||||
|
||||
public function thumb()
|
||||
{
|
||||
return $this->thumbnailUrl();
|
||||
}
|
||||
|
||||
public function mimeType()
|
||||
{
|
||||
return explode('/', $this->mime)[0];
|
||||
|
|
|
@ -12,7 +12,7 @@ class Profile extends Model
|
|||
|
||||
protected $dates = ['deleted_at'];
|
||||
protected $hidden = ['private_key'];
|
||||
protected $visible = ['username', 'name'];
|
||||
protected $visible = ['id', 'user_id', 'username', 'name'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -24,6 +25,8 @@ class AuthServiceProvider extends ServiceProvider
|
|||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
//
|
||||
Passport::routes();
|
||||
Passport::tokensExpireIn(now()->addDays(15));
|
||||
Passport::refreshTokensExpireIn(now()->addDays(30));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,11 @@ class Status extends Model
|
|||
$media = $this->firstMedia();
|
||||
$path = $media->media_path;
|
||||
$hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
|
||||
$url = Storage::url($path)."?v={$hash}";
|
||||
if(config('pixelfed.cloud_storage') == true) {
|
||||
$url = Storage::disk(config('filesystems.cloud'))->url($path)."?v={$hash}";
|
||||
} else {
|
||||
$url = Storage::url($path)."?v={$hash}";
|
||||
}
|
||||
|
||||
return url($url);
|
||||
}
|
||||
|
@ -270,6 +274,22 @@ class Status extends Model
|
|||
__('notification.commented');
|
||||
}
|
||||
|
||||
public function shareToText()
|
||||
{
|
||||
$actorName = $this->profile->username;
|
||||
|
||||
return "{$actorName} ".__('notification.shared');
|
||||
}
|
||||
|
||||
public function shareToHtml()
|
||||
{
|
||||
$actorName = $this->profile->username;
|
||||
$actorUrl = $this->profile->url();
|
||||
|
||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
|
||||
__('notification.shared');
|
||||
}
|
||||
|
||||
public function recentComments()
|
||||
{
|
||||
return $this->comments()->orderBy('created_at', 'desc')->take(3);
|
||||
|
|
|
@ -2,9 +2,36 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use Auth;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Story extends Model
|
||||
{
|
||||
//
|
||||
protected $visible = ['id'];
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(StoryItem::class);
|
||||
}
|
||||
|
||||
public function reactions()
|
||||
{
|
||||
return $this->hasMany(StoryReaction::class);
|
||||
}
|
||||
|
||||
public function views()
|
||||
{
|
||||
return $this->hasMany(StoryView::class);
|
||||
}
|
||||
|
||||
public function seen($pid = false)
|
||||
{
|
||||
$id = $pid ?? Auth::user()->profile->id;
|
||||
return $this->views()->whereProfileId($id)->exists();
|
||||
}
|
||||
}
|
||||
|
|
19
app/StoryItem.php
Normal file
19
app/StoryItem.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Storage;
|
||||
|
||||
class StoryItem extends Model
|
||||
{
|
||||
public function story()
|
||||
{
|
||||
return $this->belongsTo(Story::class);
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
return Storage::url($this->media_path);
|
||||
}
|
||||
}
|
|
@ -6,5 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class StoryReaction extends Model
|
||||
{
|
||||
//
|
||||
public function story()
|
||||
{
|
||||
return $this->belongsTo(Story::class);
|
||||
}
|
||||
}
|
||||
|
|
13
app/StoryView.php
Normal file
13
app/StoryView.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class StoryView extends Model
|
||||
{
|
||||
public function story()
|
||||
{
|
||||
return $this->belongsTo(Story::class);
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
|||
'type' => 'Document',
|
||||
'mediaType' => $media->mime,
|
||||
'url' => $media->url(),
|
||||
'name' => null,
|
||||
'name' => $media->caption
|
||||
];
|
||||
}),
|
||||
'tag' => [],
|
||||
|
|
|
@ -7,7 +7,7 @@ use League\Fractal;
|
|||
|
||||
class Follow extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Follower $follower)
|
||||
public function transform($follower)
|
||||
{
|
||||
return [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
|
|
56
app/Transformer/ActivityPub/Verb/Note.php
Normal file
56
app/Transformer/ActivityPub/Verb/Note.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\ActivityPub\Verb;
|
||||
|
||||
use App\Status;
|
||||
use League\Fractal;
|
||||
|
||||
class Note extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Status $status)
|
||||
{
|
||||
|
||||
$mentions = $status->mentions->map(function ($mention) {
|
||||
return [
|
||||
'type' => 'Mention',
|
||||
'href' => $mention->permalink(),
|
||||
'name' => $mention->emailUrl()
|
||||
];
|
||||
})->toArray();
|
||||
$hashtags = $status->hashtags->map(function ($hashtag) {
|
||||
return [
|
||||
'type' => 'Hashtag',
|
||||
'href' => $hashtag->url(),
|
||||
'name' => "#{$hashtag->name}",
|
||||
];
|
||||
})->toArray();
|
||||
$tags = array_merge($mentions, $hashtags);
|
||||
|
||||
return [
|
||||
'@context' => [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
],
|
||||
'id' => $status->url(),
|
||||
'type' => 'Note',
|
||||
'summary' => null,
|
||||
'content' => $status->rendered ?? $status->caption,
|
||||
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
||||
'published' => $status->created_at->toAtomString(),
|
||||
'url' => $status->url(),
|
||||
'attributedTo' => $status->profile->permalink(),
|
||||
'to' => $status->scopeToAudience('to'),
|
||||
'cc' => $status->scopeToAudience('cc'),
|
||||
'sensitive' => (bool) $status->is_nsfw,
|
||||
'attachment' => $status->media->map(function ($media) {
|
||||
return [
|
||||
'type' => $media->activityVerb(),
|
||||
'mediaType' => $media->mime,
|
||||
'url' => $media->url(),
|
||||
'name' => null,
|
||||
];
|
||||
})->toArray(),
|
||||
'tag' => $tags,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -7,27 +7,28 @@ 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' => null,
|
||||
'header_static' => null,
|
||||
'moved' => null,
|
||||
'fields' => null,
|
||||
'bot' => null,
|
||||
];
|
||||
}
|
||||
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' => null,
|
||||
'header_static' => null,
|
||||
'moved' => null,
|
||||
'fields' => null,
|
||||
'bot' => null,
|
||||
'software' => 'pixelfed'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ use League\Fractal;
|
|||
|
||||
class ApplicationTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'name' => '',
|
||||
'website' => null,
|
||||
];
|
||||
}
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'name' => '',
|
||||
'website' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
28
app/Transformer/Api/AttachmentTransformer.php
Normal file
28
app/Transformer/Api/AttachmentTransformer.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use League\Fractal;
|
||||
|
||||
class AttachmentTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Media $media)
|
||||
{
|
||||
return [
|
||||
'id' => $media->id,
|
||||
'type' => $media->activityVerb(),
|
||||
'url' => $media->url(),
|
||||
'remote_url' => null,
|
||||
'preview_url' => $media->thumbnailUrl(),
|
||||
'text_url' => null,
|
||||
'meta' => null,
|
||||
'description' => $media->caption,
|
||||
'license' => $media->license,
|
||||
'is_nsfw' => $media->is_nsfw,
|
||||
'orientation' => $media->orientation,
|
||||
'filter_name' => $media->filter_name,
|
||||
'filter_class' => $media->filter_class,
|
||||
'mime' => $media->mime,
|
||||
];
|
||||
}
|
||||
}
|
16
app/Transformer/Api/ContextTransformer.php
Normal file
16
app/Transformer/Api/ContextTransformer.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use League\Fractal;
|
||||
|
||||
class ContextTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'ancestors' => [],
|
||||
'descendants' => []
|
||||
];
|
||||
}
|
||||
}
|
|
@ -6,13 +6,13 @@ use League\Fractal;
|
|||
|
||||
class EmojiTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform($emoji)
|
||||
{
|
||||
return [
|
||||
'shortcode' => '',
|
||||
'static_url' => '',
|
||||
'url' => '',
|
||||
'visible_in_picker' => false
|
||||
];
|
||||
}
|
||||
public function transform($emoji)
|
||||
{
|
||||
return [
|
||||
'shortcode' => '',
|
||||
'static_url' => '',
|
||||
'url' => '',
|
||||
'visible_in_picker' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
20
app/Transformer/Api/FilterTransformer.php
Normal file
20
app/Transformer/Api/FilterTransformer.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use League\Fractal;
|
||||
|
||||
class FilterTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'id' => (string) '',
|
||||
'phrase' => (string) '',
|
||||
'context' => [],
|
||||
'expires_at' => null,
|
||||
'irreversible' => (bool) false,
|
||||
'whole_word' => (bool) false
|
||||
];
|
||||
}
|
||||
}
|
|
@ -7,11 +7,11 @@ use League\Fractal;
|
|||
|
||||
class HashtagTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Hashtag $hashtag)
|
||||
{
|
||||
return [
|
||||
'name' => $hashtag->name,
|
||||
'url' => $hashtag->url(),
|
||||
];
|
||||
}
|
||||
public function transform(Hashtag $hashtag)
|
||||
{
|
||||
return [
|
||||
'name' => $hashtag->name,
|
||||
'url' => $hashtag->url(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,20 +10,20 @@ class MediaTransformer extends Fractal\TransformerAbstract
|
|||
public function transform(Media $media)
|
||||
{
|
||||
return [
|
||||
'id' => $media->id,
|
||||
'type' => $media->activityVerb(),
|
||||
'url' => $media->url(),
|
||||
'remote_url' => null,
|
||||
'preview_url' => $media->thumbnailUrl(),
|
||||
'text_url' => null,
|
||||
'meta' => null,
|
||||
'description' => $media->caption,
|
||||
'license' => $media->license,
|
||||
'is_nsfw' => $media->is_nsfw,
|
||||
'orientation' => $media->orientation,
|
||||
'filter_name' => $media->filter_name,
|
||||
'filter_class' => $media->filter_class,
|
||||
'mime' => $media->mime,
|
||||
'id' => $media->id,
|
||||
'type' => $media->activityVerb(),
|
||||
'url' => $media->url(),
|
||||
'remote_url' => null,
|
||||
'preview_url' => $media->thumbnailUrl(),
|
||||
'text_url' => null,
|
||||
'meta' => null,
|
||||
'description' => $media->caption,
|
||||
'license' => $media->license,
|
||||
'is_nsfw' => $media->is_nsfw,
|
||||
'orientation' => $media->orientation,
|
||||
'filter_name' => $media->filter_name,
|
||||
'filter_class' => $media->filter_class,
|
||||
'mime' => $media->mime,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
|||
'follow' => 'follow',
|
||||
'mention' => 'mention',
|
||||
'reblog' => 'share',
|
||||
'share' => 'share',
|
||||
'like' => 'favourite',
|
||||
'comment' => 'comment',
|
||||
];
|
||||
|
|
42
app/Transformer/Api/ResultsTransformer.php
Normal file
42
app/Transformer/Api/ResultsTransformer.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use League\Fractal;
|
||||
|
||||
class ResultsTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'accounts',
|
||||
'statuses',
|
||||
'hashtags',
|
||||
];
|
||||
|
||||
public function transform($results)
|
||||
{
|
||||
return [
|
||||
'accounts' => [],
|
||||
'statuses' => [],
|
||||
'hashtags' => []
|
||||
];
|
||||
}
|
||||
|
||||
public function includeAccounts($results)
|
||||
{
|
||||
$accounts = $results->accounts;
|
||||
return $this->collection($accounts, new AccountTransformer());
|
||||
}
|
||||
|
||||
public function includeStatuses($results)
|
||||
{
|
||||
$statuses = $results->statuses;
|
||||
return $this->collection($statuses, new StatusTransformer());
|
||||
}
|
||||
|
||||
public function includeTags($results)
|
||||
{
|
||||
$hashtags = $status->hashtags;
|
||||
return $this->collection($hashtags, new HashtagTransformer());
|
||||
}
|
||||
}
|
27
app/Transformer/Api/StoryItemTransformer.php
Normal file
27
app/Transformer/Api/StoryItemTransformer.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\StoryItem;
|
||||
use League\Fractal;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StoryItemTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
|
||||
public function transform(StoryItem $item)
|
||||
{
|
||||
return [
|
||||
'id' => (string) Str::uuid(),
|
||||
'type' => $item->type,
|
||||
'length' => $item->duration,
|
||||
'src' => $item->url(),
|
||||
'preview' => null,
|
||||
'link' => null,
|
||||
'linkText' => null,
|
||||
'time' => $item->updated_at->format('U'),
|
||||
'seen' => $item->story->seen(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
34
app/Transformer/Api/StoryTransformer.php
Normal file
34
app/Transformer/Api/StoryTransformer.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\Story;
|
||||
use League\Fractal;
|
||||
|
||||
class StoryTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
protected $defaultIncludes = [
|
||||
'items',
|
||||
];
|
||||
|
||||
public function transform(Story $story)
|
||||
{
|
||||
return [
|
||||
'id' => $story->id,
|
||||
'photo' => $story->profile->avatarUrl(),
|
||||
'name' => '',
|
||||
'link' => '',
|
||||
'lastUpdated' => $story->updated_at->format('U'),
|
||||
'seen' => $story->seen(),
|
||||
'items' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function includeItems(Story $story)
|
||||
{
|
||||
$items = $story->items;
|
||||
|
||||
return $this->collection($items, new StoryItemTransformer());
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Util\ActivityPub;
|
||||
|
||||
use Cache, DB, Log, Redis, Validator;
|
||||
use Cache, DB, Log, Purify, Redis, Validator;
|
||||
use App\{
|
||||
Activity,
|
||||
Follower,
|
||||
|
@ -16,6 +16,10 @@ use Carbon\Carbon;
|
|||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Jobs\LikePipeline\LikePipeline;
|
||||
|
||||
use App\Util\ActivityPub\Validator\{
|
||||
Follow
|
||||
};
|
||||
|
||||
class Inbox
|
||||
{
|
||||
protected $headers;
|
||||
|
@ -35,30 +39,6 @@ class Inbox
|
|||
$this->handleVerb();
|
||||
}
|
||||
|
||||
public function authenticatePayload()
|
||||
{
|
||||
try {
|
||||
$signature = Helpers::validateSignature($this->headers, $this->payload);
|
||||
$payload = Helpers::validateObject($this->payload);
|
||||
if($signature == false) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
$this->payloadLogger();
|
||||
}
|
||||
|
||||
public function payloadLogger()
|
||||
{
|
||||
$logger = new Activity;
|
||||
$logger->data = json_encode($this->payload);
|
||||
$logger->save();
|
||||
$this->logger = $logger;
|
||||
Log::info('AP:inbox:activity:new:'.$this->logger->id);
|
||||
$this->handleVerb();
|
||||
}
|
||||
|
||||
public function handleVerb()
|
||||
{
|
||||
$verb = $this->payload['type'];
|
||||
|
@ -76,6 +56,7 @@ class Inbox
|
|||
break;
|
||||
|
||||
case 'Accept':
|
||||
if(Accept::validate($this->payload) == false) { return; }
|
||||
$this->handleAcceptActivity();
|
||||
break;
|
||||
|
||||
|
@ -171,7 +152,8 @@ class Inbox
|
|||
$caption = str_limit(strip_tags($activity['content']), config('pixelfed.max_caption_length'));
|
||||
$status = new Status;
|
||||
$status->profile_id = $actor->id;
|
||||
$status->caption = $caption;
|
||||
$status->caption = strip_tags($caption);
|
||||
$status->rendered = Purify::clean($caption);
|
||||
$status->visibility = $status->scope = 'public';
|
||||
$status->uri = $url;
|
||||
$status->url = $url;
|
||||
|
@ -275,13 +257,10 @@ class Inbox
|
|||
$obj = $this->payload['object'];
|
||||
if(is_string($obj) && Helpers::validateUrl($obj)) {
|
||||
// actor object detected
|
||||
|
||||
// todo delete actor
|
||||
} else if (is_array($obj) && isset($obj['type']) && $obj['type'] == 'Tombstone') {
|
||||
// tombstone detected
|
||||
$status = Status::whereUri($obj['id'])->first();
|
||||
if($status == null) {
|
||||
return;
|
||||
}
|
||||
$status = Status::whereUri($obj['id'])->firstOrFail();
|
||||
$status->forceDelete();
|
||||
}
|
||||
}
|
||||
|
|
28
app/Util/ActivityPub/Validator/Announce.php
Normal file
28
app/Util/ActivityPub/Validator/Announce.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\ActivityPub\Validator;
|
||||
|
||||
use Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Announce {
|
||||
|
||||
public static function validate($payload)
|
||||
{
|
||||
$valid = Validator::make($payload, [
|
||||
'@context' => 'required',
|
||||
'id' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['Announce'])
|
||||
],
|
||||
'actor' => 'required|url|active_url',
|
||||
'published' => 'required|date',
|
||||
'to' => 'required',
|
||||
'cc' => 'required',
|
||||
'object' => 'required|url|active_url'
|
||||
])->passes();
|
||||
|
||||
return $valid;
|
||||
}
|
||||
}
|
25
app/Util/ActivityPub/Validator/Follow.php
Normal file
25
app/Util/ActivityPub/Validator/Follow.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\ActivityPub\Validator;
|
||||
|
||||
use Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Follow {
|
||||
|
||||
public static function validate($payload)
|
||||
{
|
||||
$valid = Validator::make($payload, [
|
||||
'@context' => 'required',
|
||||
'id' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['Follow'])
|
||||
],
|
||||
'actor' => 'required|url|active_url',
|
||||
'object' => 'required|url|active_url'
|
||||
])->passes();
|
||||
|
||||
return $valid;
|
||||
}
|
||||
}
|
25
app/Util/ActivityPub/Validator/Like.php
Normal file
25
app/Util/ActivityPub/Validator/Like.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\ActivityPub\Validator;
|
||||
|
||||
use Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Like {
|
||||
|
||||
public static function validate($payload)
|
||||
{
|
||||
$valid = Validator::make($payload, [
|
||||
'@context' => 'required',
|
||||
'id' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['Like'])
|
||||
],
|
||||
'actor' => 'required|url|active_url',
|
||||
'object' => 'required|url|active_url'
|
||||
])->passes();
|
||||
|
||||
return $valid;
|
||||
}
|
||||
}
|
|
@ -17,15 +17,6 @@ class RestrictedNames
|
|||
'contact-us',
|
||||
'contact_us',
|
||||
'copyright',
|
||||
'd',
|
||||
'dashboard',
|
||||
'dev',
|
||||
'developer',
|
||||
'developers',
|
||||
'discover',
|
||||
'discovers',
|
||||
'doc',
|
||||
'docs',
|
||||
'download',
|
||||
'domainadmin',
|
||||
'domainadministrator',
|
||||
|
@ -41,10 +32,7 @@ class RestrictedNames
|
|||
'guests',
|
||||
'hostmaster',
|
||||
'hostmaster',
|
||||
'image',
|
||||
'images',
|
||||
'imap',
|
||||
'img',
|
||||
'info',
|
||||
'info',
|
||||
'is',
|
||||
|
@ -57,7 +45,6 @@ class RestrictedNames
|
|||
'mailerdaemon',
|
||||
'marketing',
|
||||
'me',
|
||||
'media',
|
||||
'mis',
|
||||
'mx',
|
||||
'new',
|
||||
|
@ -82,7 +69,6 @@ class RestrictedNames
|
|||
'pop3',
|
||||
'postmaster',
|
||||
'pricing',
|
||||
'privacy',
|
||||
'root',
|
||||
'sales',
|
||||
'security',
|
||||
|
@ -96,7 +82,6 @@ class RestrictedNames
|
|||
'sys',
|
||||
'sysadmin',
|
||||
'system',
|
||||
'terms',
|
||||
'tutorial',
|
||||
'tutorials',
|
||||
'usenet',
|
||||
|
@ -121,34 +106,65 @@ class RestrictedNames
|
|||
'account',
|
||||
'api',
|
||||
'auth',
|
||||
'bartender',
|
||||
'broadcast',
|
||||
'broadcaster',
|
||||
'booth',
|
||||
'bouncer',
|
||||
'c',
|
||||
'css',
|
||||
'checkpoint',
|
||||
'collection',
|
||||
'collections',
|
||||
'c',
|
||||
'costar',
|
||||
'costars',
|
||||
'cdn',
|
||||
'd',
|
||||
'dashboard',
|
||||
'deck',
|
||||
'dev',
|
||||
'developer',
|
||||
'developers',
|
||||
'discover',
|
||||
'discovers',
|
||||
'dj',
|
||||
'doc',
|
||||
'docs',
|
||||
'docs',
|
||||
'drive',
|
||||
'driver',
|
||||
'error',
|
||||
'explore',
|
||||
'font',
|
||||
'fonts',
|
||||
'gdpr',
|
||||
'home',
|
||||
'help',
|
||||
'helpcenter',
|
||||
'help-center',
|
||||
'help_center',
|
||||
'help_center_',
|
||||
'help-center-',
|
||||
'help-center_',
|
||||
'help_center-',
|
||||
'i',
|
||||
'img',
|
||||
'imgs',
|
||||
'image',
|
||||
'images',
|
||||
'js',
|
||||
'legal',
|
||||
'live',
|
||||
'login',
|
||||
'logout',
|
||||
'media',
|
||||
'menu',
|
||||
'official',
|
||||
'p',
|
||||
'photo',
|
||||
'photos',
|
||||
'password',
|
||||
'privacy',
|
||||
'reset',
|
||||
'report',
|
||||
'reports',
|
||||
|
@ -161,20 +177,28 @@ class RestrictedNames
|
|||
'statuses',
|
||||
'site',
|
||||
'sites',
|
||||
'stage',
|
||||
'static',
|
||||
'story',
|
||||
'stories',
|
||||
'support',
|
||||
'svg',
|
||||
'svgs',
|
||||
'terms',
|
||||
'telescope',
|
||||
'timeline',
|
||||
'timelines',
|
||||
'tour',
|
||||
'user',
|
||||
'users',
|
||||
'username',
|
||||
'usernames',
|
||||
'vendor',
|
||||
'waiter',
|
||||
'ws',
|
||||
'wss',
|
||||
'www',
|
||||
'valet',
|
||||
'400',
|
||||
'401',
|
||||
'403',
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"ext-bcmath": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"beyondcode/laravel-self-diagnosis": "^1.0.2",
|
||||
"bitverse/identicon": "^1.1",
|
||||
"doctrine/dbal": "^2.7",
|
||||
|
|
|
@ -42,7 +42,7 @@ return [
|
|||
],
|
||||
|
||||
'api' => [
|
||||
'driver' => 'token',
|
||||
'driver' => 'passport',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
|
|
@ -65,6 +65,21 @@ return [
|
|||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
],
|
||||
|
||||
'spaces' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('DO_SPACES_KEY'),
|
||||
'secret' => env('DO_SPACES_SECRET'),
|
||||
'endpoint' => env('DO_SPACES_ENDPOINT'),
|
||||
'region' => env('DO_SPACES_REGION'),
|
||||
'bucket' => env('DO_SPACES_BUCKET'),
|
||||
'visibility' => 'public',
|
||||
'options' => [
|
||||
'CacheControl' => 'max-age=31536000'
|
||||
],
|
||||
'root' => env('DO_SPACES_ROOT','/'),
|
||||
'url' => str_replace(env('DO_SPACES_REGION'),env('DO_SPACES_BUCKET').'.'.env('DO_SPACES_REGION'),str_replace("digitaloceanspaces","cdn.digitaloceanspaces",env('DO_SPACES_ENDPOINT'))),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -23,7 +23,7 @@ return [
|
|||
| This value is the version of your PixelFed instance.
|
||||
|
|
||||
*/
|
||||
'version' => '0.7.7',
|
||||
'version' => '0.7.10',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -177,6 +177,38 @@ return [
|
|||
*/
|
||||
'image_quality' => (int) env('IMAGE_QUALITY', 80),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Account deletion
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Enable account deletion.
|
||||
|
|
||||
*/
|
||||
'account_deletion' => env('ACCOUNT_DELETION', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Account deletion after X days
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set account deletion queue after X days, set to false to delete accounts
|
||||
| immediately.
|
||||
|
|
||||
*/
|
||||
'account_delete_after' => env('ACCOUNT_DELETE_AFTER', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Enable Cloud Storage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Store media on object storage like S3, Digital Ocean Spaces, Rackspace
|
||||
|
|
||||
*/
|
||||
'cloud_storage' => env('PF_ENABLE_CLOUD', false),
|
||||
|
||||
|
||||
'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
|
||||
'enforce_account_limit' => env('LIMIT_ACCOUNT_SIZE', true),
|
||||
'ap_inbox' => env('ACTIVITYPUB_INBOX', false),
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateProfilesTableUseTextForBio extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('profiles', function (Blueprint $table) {
|
||||
$table->text('bio')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('profiles', function (Blueprint $table) {
|
||||
$table->string('bio')->nullable()->change();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateProfilesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('profiles', function (Blueprint $table) {
|
||||
$table->boolean('unlisted')->default(false)->index()->after('bio');
|
||||
$table->boolean('cw')->default(false)->index()->after('unlisted');
|
||||
$table->boolean('no_autolink')->default(false)->index()->after('cw');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$table->dropColumn('unlisted');
|
||||
$table->dropColumn('cw');
|
||||
$table->dropColumn('no_autolink');
|
||||
}
|
||||
}
|
77
database/migrations/2019_01_12_054413_stories.php
Normal file
77
database/migrations/2019_01_12_054413_stories.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class Stories extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('story_items', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('story_id')->unsigned()->index();
|
||||
$table->string('media_path')->nullable();
|
||||
$table->string('media_url')->nullable();
|
||||
$table->tinyInteger('duration')->unsigned();
|
||||
$table->string('filter')->nullable();
|
||||
$table->string('link_url')->nullable()->index();
|
||||
$table->string('link_text')->nullable();
|
||||
$table->tinyInteger('order')->unsigned()->nullable();
|
||||
$table->string('type')->default('photo');
|
||||
$table->json('layers')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('story_views', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('story_id')->unsigned()->index();
|
||||
$table->bigInteger('profile_id')->unsigned()->index();
|
||||
$table->unique(['story_id', 'profile_id']);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::table('stories', function (Blueprint $table) {
|
||||
$table->string('title')->nullable()->after('profile_id');
|
||||
$table->boolean('preview_photo')->default(false)->after('title');
|
||||
$table->boolean('local_only')->default(false)->after('preview_photo');
|
||||
$table->boolean('is_live')->default(false)->after('local_only');
|
||||
$table->string('broadcast_url')->nullable()->after('is_live');
|
||||
$table->string('broadcast_key')->nullable()->after('broadcast_url');
|
||||
});
|
||||
|
||||
Schema::table('story_reactions', function (Blueprint $table) {
|
||||
$table->bigInteger('story_id')->unsigned()->index()->after('profile_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('story_items');
|
||||
Schema::dropIfExists('story_views');
|
||||
|
||||
Schema::table('stories', function (Blueprint $table) {
|
||||
$table->dropColumn('title');
|
||||
$table->dropColumn('preview_photo');
|
||||
$table->dropColumn('local_only');
|
||||
$table->dropColumn('is_live');
|
||||
$table->dropColumn('broadcast_url');
|
||||
$table->dropColumn('broadcast_key');
|
||||
});
|
||||
|
||||
Schema::table('story_reactions', function (Blueprint $table) {
|
||||
$table->dropColumn('story_id');
|
||||
});
|
||||
}
|
||||
}
|
9047
package-lock.json
generated
9047
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -15,9 +15,11 @@
|
|||
"bootstrap": "^4.2.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"jquery": "^3.2",
|
||||
"laravel-mix": "^2.1.14",
|
||||
"lodash": "^4.17.11",
|
||||
"popper.js": "^1.14.6",
|
||||
"resolve-url-loader": "^2.3.1",
|
||||
"sass": "^1.15.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"vue": "^2.5.21",
|
||||
"vue-template-compiler": "^2.5.21"
|
||||
},
|
||||
|
@ -27,9 +29,12 @@
|
|||
"filesize": "^3.6.1",
|
||||
"infinite-scroll": "^3.0.4",
|
||||
"laravel-echo": "^1.5.2",
|
||||
"laravel-mix": "^4.0.12",
|
||||
"node-sass": "^4.11.0",
|
||||
"opencollective": "^1.0.3",
|
||||
"opencollective-postinstall": "^2.0.1",
|
||||
"plyr": "^3.4.7",
|
||||
"promise-polyfill": "8.1.0",
|
||||
"pusher-js": "^4.2.2",
|
||||
"readmore-js": "^2.2.1",
|
||||
"socket.io-client": "^2.2.0",
|
||||
|
|
BIN
public/css/app.css
vendored
BIN
public/css/app.css
vendored
Binary file not shown.
BIN
public/js/activity.js
vendored
BIN
public/js/activity.js
vendored
Binary file not shown.
BIN
public/js/app.js
vendored
BIN
public/js/app.js
vendored
Binary file not shown.
BIN
public/js/components.js
vendored
BIN
public/js/components.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
Binary file not shown.
1
public/svg/403.svg
Executable file
1
public/svg/403.svg
Executable file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.5 KiB |
1
public/svg/404.svg
Executable file
1
public/svg/404.svg
Executable file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024"><defs><linearGradient id="a" x1="50.31%" x2="50%" y1="74.74%" y2="0%"><stop offset="0%" stop-color="#FFE98A"/><stop offset="67.7%" stop-color="#B63E59"/><stop offset="100%" stop-color="#68126F"/></linearGradient><circle id="c" cx="603" cy="682" r="93"/><filter id="b" width="203.2%" height="203.2%" x="-51.6%" y="-51.6%" filterUnits="objectBoundingBox"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="32"/><feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><linearGradient id="d" x1="49.48%" x2="49.87%" y1="11.66%" y2="77.75%"><stop offset="0%" stop-color="#F7EAB9"/><stop offset="100%" stop-color="#E5765E"/></linearGradient><linearGradient id="e" x1="91.59%" x2="66.97%" y1="5.89%" y2="100%"><stop offset="0%" stop-color="#A22A50"/><stop offset="100%" stop-color="#EE7566"/></linearGradient><linearGradient id="f" x1="49.48%" x2="49.61%" y1="11.66%" y2="98.34%"><stop offset="0%" stop-color="#F7EAB9"/><stop offset="100%" stop-color="#E5765E"/></linearGradient><linearGradient id="g" x1="78.5%" x2="36.4%" y1="106.76%" y2="26.41%"><stop offset="0%" stop-color="#A22A50"/><stop offset="100%" stop-color="#EE7566"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><rect width="1024" height="1024" fill="url(#a)"/><use fill="black" filter="url(#b)" xlink:href="#c"/><use fill="#FFF6CB" xlink:href="#c"/><g fill="#FFFFFF" opacity=".3" transform="translate(14 23)"><circle cx="203" cy="255" r="3" fill-opacity=".4"/><circle cx="82" cy="234" r="2"/><circle cx="22" cy="264" r="2" opacity=".4"/><circle cx="113" cy="65" r="3"/><circle cx="202" cy="2" r="2"/><circle cx="2" cy="114" r="2"/><circle cx="152" cy="144" r="2"/><circle cx="362" cy="224" r="2"/><circle cx="453" cy="65" r="3" opacity=".4"/><circle cx="513" cy="255" r="3"/><circle cx="593" cy="115" r="3"/><circle cx="803" cy="5" r="3" opacity=".4"/><circle cx="502" cy="134" r="2"/><circle cx="832" cy="204" r="2"/><circle cx="752" cy="114" r="2"/><circle cx="933" cy="255" r="3" opacity=".4"/><circle cx="703" cy="225" r="3"/><circle cx="903" cy="55" r="3"/><circle cx="982" cy="144" r="2"/><circle cx="632" cy="14" r="2"/></g><g transform="translate(0 550)"><path fill="#8E2C15" d="M259 5.47c0 5.33 3.33 9.5 10 12.5s9.67 9.16 9 18.5h1c.67-6.31 1-11.8 1-16.47 8.67 0 13.33-1.33 14-4 .67 4.98 1.67 8.3 3 9.97 1.33 1.66 2 5.16 2 10.5h1c0-5.65.33-9.64 1-11.97 1-3.5 4-10.03-1-14.53S295 7 290 3c-5-4-10-3-13 2s-5 7-9 7-5-3.53-5-5.53c0-2 2-5-1.5-5s-7.5 0-7.5 2c0 1.33 1.67 2 5 2z"/><path fill="url(#d)" d="M1024 390H0V105.08C77.3 71.4 155.26 35 297.4 35c250 0 250.76 125.25 500 125 84.03-.08 160.02-18.2 226.6-40.93V390z"/><path fill="url(#d)" d="M1024 442H0V271.82c137.51-15.4 203.1-50.49 356.67-60.1C555.24 199.3 606.71 86.59 856.74 86.59c72.78 0 124.44 10.62 167.26 25.68V442z"/><path fill="url(#e)" d="M1024 112.21V412H856.91c99.31-86.5 112.63-140.75 39.97-162.78C710.24 192.64 795.12 86.58 856.9 86.58c72.7 0 124.3 10.6 167.09 25.63z"/><path fill="url(#e)" d="M1024 285.32V412H857c99.31-86.6 112.63-140.94 39.97-163L1024 285.32z"/><path fill="url(#f)" d="M0 474V223.93C67.12 190.69 129.55 155 263 155c250 0 331.46 162.6 530 175 107.42 6.71 163-26.77 231-58.92V474H0z"/><path fill="url(#e)" d="M353.02 474H0V223.93C67.12 190.69 129.55 155 263 155c71.14 0 151.5 12.76 151.5 70.5 0 54.5-45.5 79.72-112.5 109-82.26 35.95-54.57 111.68 51.02 139.5z"/><path fill="url(#g)" d="M353.02 474H0v-14.8l302-124.7c-82.26 35.95-54.57 111.68 51.02 139.5z"/></g><g fill="#FFFFFF" opacity=".2" transform="translate(288 523)"><circle cx="250" cy="110" r="110"/><circle cx="420" cy="78" r="60"/><circle cx="70" cy="220" r="70"/></g><g fill="#FFFFFF" fill-rule="nonzero" opacity=".08" transform="translate(135 316)"><path d="M10 80.22a14.2 14.2 0 0 1 20 0 14.2 14.2 0 0 0 20 0l20-19.86a42.58 42.58 0 0 1 60 0l15 14.9a21.3 21.3 0 0 0 30 0 21.3 21.3 0 0 1 30 0l.9.9A47.69 47.69 0 0 1 220 110H0v-5.76c0-9.02 3.6-17.67 10-24.02zm559.1-66.11l5.9-5.86c11.07-11 28.93-11 40 0l10 9.94a14.19 14.19 0 0 0 20 0 14.19 14.19 0 0 1 20 0 16.36 16.36 0 0 0 21.3 1.5l8.7-6.47a33.47 33.47 0 0 1 40 0l4.06 3.03A39.6 39.6 0 0 1 755 48H555a47.77 47.77 0 0 1 14.1-33.89z"/></g></g></svg>
|
After Width: | Height: | Size: 4.2 KiB |
1
public/svg/500.svg
Executable file
1
public/svg/500.svg
Executable file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 15 KiB |
1
public/svg/503.svg
Executable file
1
public/svg/503.svg
Executable file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.4 KiB |
93
resources/assets/js/components.js
vendored
93
resources/assets/js/components.js
vendored
|
@ -3,6 +3,7 @@ import BootstrapVue from 'bootstrap-vue'
|
|||
import InfiniteLoading from 'vue-infinite-loading';
|
||||
import Loading from 'vue-loading-overlay';
|
||||
import VueTimeago from 'vue-timeago';
|
||||
//import {Howl, Howler} from 'howler';
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
Vue.use(InfiniteLoading);
|
||||
|
@ -29,7 +30,6 @@ try {
|
|||
document.createEvent("TouchEvent");
|
||||
$('body').addClass('touch');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
window.InfiniteScroll = require('infinite-scroll');
|
||||
|
@ -67,44 +67,49 @@ window.pixelfed.n = {};
|
|||
|
||||
Vue.component(
|
||||
'photo-presenter',
|
||||
require('./components/presenter/PhotoPresenter.vue')
|
||||
require('./components/presenter/PhotoPresenter.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'video-presenter',
|
||||
require('./components/presenter/VideoPresenter.vue')
|
||||
require('./components/presenter/VideoPresenter.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'photo-album-presenter',
|
||||
require('./components/presenter/PhotoAlbumPresenter.vue')
|
||||
require('./components/presenter/PhotoAlbumPresenter.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'video-album-presenter',
|
||||
require('./components/presenter/VideoAlbumPresenter.vue')
|
||||
require('./components/presenter/VideoAlbumPresenter.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'mixed-album-presenter',
|
||||
require('./components/presenter/MixedAlbumPresenter.vue')
|
||||
require('./components/presenter/MixedAlbumPresenter.vue').default
|
||||
);
|
||||
|
||||
// Vue.component(
|
||||
// 'micro',
|
||||
// require('./components/Micro.vue')
|
||||
// require('./components/Micro.vue').default
|
||||
// );
|
||||
|
||||
Vue.component(
|
||||
'follow-suggestions',
|
||||
require('./components/FollowSuggestions.vue')
|
||||
require('./components/FollowSuggestions.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'discover-component',
|
||||
require('./components/DiscoverComponent.vue')
|
||||
require('./components/DiscoverComponent.vue').default
|
||||
);
|
||||
|
||||
// Vue.component(
|
||||
// 'profile',
|
||||
// require('./components/Profile.vue').default
|
||||
// );
|
||||
|
||||
// Vue.component(
|
||||
// 'circle-panel',
|
||||
// require('./components/CirclePanel.vue')
|
||||
|
@ -112,53 +117,55 @@ Vue.component(
|
|||
|
||||
Vue.component(
|
||||
'post-component',
|
||||
require('./components/PostComponent.vue')
|
||||
require('./components/PostComponent.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'post-comments',
|
||||
require('./components/PostComments.vue')
|
||||
require('./components/PostComments.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'timeline',
|
||||
require('./components/Timeline.vue')
|
||||
require('./components/Timeline.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'passport-clients',
|
||||
require('./components/passport/Clients.vue')
|
||||
);
|
||||
// Vue.component(
|
||||
// 'passport-clients',
|
||||
// require('./components/passport/Clients.vue').default
|
||||
// );
|
||||
|
||||
Vue.component(
|
||||
'passport-authorized-clients',
|
||||
require('./components/passport/AuthorizedClients.vue')
|
||||
);
|
||||
// Vue.component(
|
||||
// 'passport-authorized-clients',
|
||||
// require('./components/passport/AuthorizedClients.vue').default
|
||||
// );
|
||||
|
||||
Vue.component(
|
||||
'passport-personal-access-tokens',
|
||||
require('./components/passport/PersonalAccessTokens.vue')
|
||||
);
|
||||
// Vue.component(
|
||||
// 'passport-personal-access-tokens',
|
||||
// require('./components/passport/PersonalAccessTokens.vue').default
|
||||
// );
|
||||
|
||||
window.pixelfed.copyToClipboard = (str) => {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = str;
|
||||
el.setAttribute('readonly', '');
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = '-9999px';
|
||||
document.body.appendChild(el);
|
||||
const selected =
|
||||
document.getSelection().rangeCount > 0
|
||||
? document.getSelection().getRangeAt(0)
|
||||
: false;
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
if (selected) {
|
||||
document.getSelection().removeAllRanges();
|
||||
document.getSelection().addRange(selected);
|
||||
}
|
||||
};
|
||||
//import 'promise-polyfill/src/polyfill';
|
||||
|
||||
// window.pixelfed.copyToClipboard = (str) => {
|
||||
// const el = document.createElement('textarea');
|
||||
// el.value = str;
|
||||
// el.setAttribute('readonly', '');
|
||||
// el.style.position = 'absolute';
|
||||
// el.style.left = '-9999px';
|
||||
// document.body.appendChild(el);
|
||||
// const selected =
|
||||
// document.getSelection().rangeCount > 0
|
||||
// ? document.getSelection().getRangeAt(0)
|
||||
// : false;
|
||||
// el.select();
|
||||
// document.execCommand('copy');
|
||||
// document.body.removeChild(el);
|
||||
// if (selected) {
|
||||
// document.getSelection().removeAllRanges();
|
||||
// document.getSelection().addRange(selected);
|
||||
// }
|
||||
// };
|
||||
|
||||
$(document).ready(function() {
|
||||
$(function () {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<style>
|
||||
<style scoped>
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ export default {
|
|||
axios.get(url)
|
||||
.then(response => {
|
||||
let self = this;
|
||||
this.results = response.data.data;
|
||||
this.results = _.reverse(response.data.data);
|
||||
this.pagination = response.data.meta.pagination;
|
||||
if(this.results.length > 0) {
|
||||
$('.load-more-link').removeClass('d-none');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<style>
|
||||
<style scoped>
|
||||
#l-modal .modal-body,
|
||||
#s-modal .modal-body {
|
||||
max-height: 70vh;
|
||||
|
@ -496,18 +496,21 @@ export default {
|
|||
},
|
||||
|
||||
deletePost() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
var result = confirm('Are you sure you want to delete this post?');
|
||||
if (result) {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
axios.post('/i/delete', {
|
||||
type: 'status',
|
||||
item: this.status.id
|
||||
}).then(res => {
|
||||
swal('Success', 'You have successfully deleted this post', 'success');
|
||||
}).catch(err => {
|
||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||
});
|
||||
axios.post('/i/delete', {
|
||||
type: 'status',
|
||||
item: this.status.id
|
||||
}).then(res => {
|
||||
swal('Success', 'You have successfully deleted this post', 'success');
|
||||
}).catch(err => {
|
||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
40
resources/assets/js/components/Stories.vue
Normal file
40
resources/assets/js/components/Stories.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-body" id="stories">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style type="text/css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
stories: [],
|
||||
}
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchStories() {
|
||||
axios.get('/api/v2/stories')
|
||||
.then(res => {
|
||||
this.stories = res.data
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -2,11 +2,6 @@
|
|||
<div class="container" style="">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-lg-8 pt-2 px-0 my-3 timeline order-2 order-md-1">
|
||||
<div class="loader text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4 status-card card-md-rounded-0" :data-status-id="status.id" v-for="(status, index) in feed" :key="status.id">
|
||||
|
||||
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||
|
@ -97,6 +92,15 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<infinite-loading @infinite="infiniteTimeline">
|
||||
<div slot="no-more" class="font-weight-bold text-light">No more posts to load</div>
|
||||
<div slot="no-results" class="font-weight-bold text-light">No posts found</div>
|
||||
</infinite-loading>
|
||||
-->
|
||||
<div class="pagination d-none">
|
||||
<p class="btn btn-outline-secondary font-weight-bold btn-block" v-on:click="loadMore">Load more posts</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-lg-4 pt-2 my-3 order-1 order-md-2">
|
||||
|
@ -185,7 +189,7 @@
|
|||
<div class="container pb-5">
|
||||
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
|
||||
<a href="/site/about" class="text-dark pr-2">About Us</a>
|
||||
<a href="/site/help" class="text-dark pr-2">Support</a>
|
||||
<a href="/site/help" class="text-dark pr-2">Help</a>
|
||||
<a href="/site/open-source" class="text-dark pr-2">Open Source</a>
|
||||
<a href="/site/language" class="text-dark pr-2">Language</a>
|
||||
<a href="/site/terms" class="text-dark pr-2">Terms</a>
|
||||
|
@ -202,7 +206,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style type="text/css">
|
||||
<style type="text/css" scoped>
|
||||
.postPresenterContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -220,7 +224,7 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
page: 2,
|
||||
feed: [],
|
||||
profile: {},
|
||||
scope: window.location.pathname,
|
||||
|
@ -229,6 +233,7 @@
|
|||
notifications: {},
|
||||
stories: {},
|
||||
suggestions: {},
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -241,7 +246,6 @@
|
|||
},
|
||||
|
||||
updated() {
|
||||
this.scroll();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -262,11 +266,10 @@
|
|||
},
|
||||
|
||||
fetchTimelineApi() {
|
||||
let homeTimeline = '/api/v1/timelines/home?page=' + this.page;
|
||||
let localTimeline = '/api/v1/timelines/public?page=' + this.page;
|
||||
let homeTimeline = '/api/v1/timelines/home?page=1';
|
||||
let localTimeline = '/api/v1/timelines/public?page=1';
|
||||
let apiUrl = this.scope == '/' ? homeTimeline : localTimeline;
|
||||
axios.get(apiUrl).then(res => {
|
||||
$('.timeline .loader').addClass('d-none');
|
||||
let data = res.data;
|
||||
this.feed.push(...data);
|
||||
let ids = data.map(status => status.id);
|
||||
|
@ -274,11 +277,62 @@
|
|||
if(this.page == 1) {
|
||||
this.max_id = Math.max(...ids);
|
||||
}
|
||||
this.page++;
|
||||
$('.timeline .pagination').removeClass('d-none');
|
||||
this.loading = false;
|
||||
}).catch(err => {
|
||||
});
|
||||
},
|
||||
|
||||
infiniteTimeline($state) {
|
||||
let homeTimeline = '/api/v1/timelines/home';
|
||||
let localTimeline = '/api/v1/timelines/public';
|
||||
let apiUrl = this.scope == '/' ? homeTimeline : localTimeline;
|
||||
axios.get(apiUrl, {
|
||||
params: {
|
||||
page: this.page,
|
||||
},
|
||||
}).then(res => {
|
||||
if (res.data.length && this.loading == false) {
|
||||
let data = res.data;
|
||||
this.feed.push(...data);
|
||||
let ids = data.map(status => status.id);
|
||||
this.min_id = Math.min(...ids);
|
||||
if(this.page == 1) {
|
||||
this.max_id = Math.max(...ids);
|
||||
}
|
||||
this.page += 1;
|
||||
$state.loaded();
|
||||
this.loading = false;
|
||||
} else {
|
||||
$state.complete();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
let homeTimeline = '/api/v1/timelines/home';
|
||||
let localTimeline = '/api/v1/timelines/public';
|
||||
let apiUrl = this.scope == '/' ? homeTimeline : localTimeline;
|
||||
axios.get(apiUrl, {
|
||||
params: {
|
||||
page: this.page,
|
||||
},
|
||||
}).then(res => {
|
||||
if (res.data.length && this.loading == false) {
|
||||
let data = res.data;
|
||||
this.feed.push(...data);
|
||||
let ids = data.map(status => status.id);
|
||||
this.min_id = Math.min(...ids);
|
||||
if(this.page == 1) {
|
||||
this.max_id = Math.max(...ids);
|
||||
}
|
||||
this.page += 1;
|
||||
this.loading = false;
|
||||
} else {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
fetchNotifications() {
|
||||
axios.get('/api/v1/notifications')
|
||||
.then(res => {
|
||||
|
@ -288,16 +342,6 @@
|
|||
});
|
||||
},
|
||||
|
||||
scroll() {
|
||||
window.onscroll = () => {
|
||||
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight == document.documentElement.offsetHeight;
|
||||
|
||||
if (bottomOfWindow) {
|
||||
this.fetchTimelineApi();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
reportUrl(status) {
|
||||
let type = status.in_reply_to ? 'comment' : 'post';
|
||||
let id = status.id;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<source :src="media.url" :type="media.mime">
|
||||
</video>
|
||||
|
||||
<img v-else-if="media.type == 'Image'" slot="img" class="d-block img-fluid w-100" :src="media.url" :alt="media.description">
|
||||
<img v-else-if="media.type == 'Image'" slot="img" class="d-block img-fluid w-100" :src="media.url" :alt="media.description" :title="media.description">
|
||||
|
||||
<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
|||
<source :src="media.url" :type="media.mime">
|
||||
</video>
|
||||
|
||||
<img v-else-if="media.type == 'Image'" slot="img" class="d-block img-fluid w-100" :src="media.url" :alt="media.description">
|
||||
<img v-else-if="media.type == 'Image'" slot="img" class="d-block img-fluid w-100" :src="media.url" :alt="media.description" :title="media.description">
|
||||
|
||||
<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
:interval="0"
|
||||
>
|
||||
<b-carousel-slide v-for="(img, index) in status.media_attachments" :key="img.id">
|
||||
<img slot="img" class="d-block img-fluid w-100" :src="img.url" :alt="img.description">
|
||||
<img slot="img" class="d-block img-fluid w-100" :src="img.url" :alt="img.description" :title="img.description">
|
||||
</b-carousel-slide>
|
||||
</b-carousel>
|
||||
</details>
|
||||
|
@ -27,7 +27,7 @@
|
|||
:interval="0"
|
||||
>
|
||||
<b-carousel-slide v-for="(img, index) in status.media_attachments" :key="img.id">
|
||||
<img slot="img" class="d-block img-fluid w-100" :src="img.url" :alt="img.description">
|
||||
<img slot="img" class="d-block img-fluid w-100" :src="img.url" :alt="img.description" :title="img.description">
|
||||
</b-carousel-slide>
|
||||
</b-carousel>
|
||||
</div>
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
<p class="font-weight-light">(click to show)</p>
|
||||
</summary>
|
||||
<a class="max-hide-overflow" :href="status.url" :class="status.media_attachments[0].filter_class">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url" :alt="status.media_attachments[0].description" :title="status.media_attachments[0].description">
|
||||
</a>
|
||||
</details>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div :class="status.media_attachments[0].filter_class">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url" :alt="status.media_attachments[0].description" :title="status.media_attachments[0].description">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/*! Instagram.css v0.1.3 | MIT License | github.com/picturepan2/instagram.css */
|
||||
[class*="filter"] {
|
||||
[class*="filter-"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[class*="filter"]::before {
|
||||
[class*="filter-"]::before {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
|
|
7
resources/assets/sass/custom.scss
vendored
7
resources/assets/sass/custom.scss
vendored
|
@ -456,6 +456,13 @@ details summary::-webkit-details-marker {
|
|||
border-bottom-color:#dc3545 !important;
|
||||
}
|
||||
|
||||
#previewAvatar {
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.img-thumbnail {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
|
223
resources/views/admin/instances/home.blade.php
Normal file
223
resources/views/admin/instances/home.blade.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
@extends('admin.partial.template')
|
||||
|
||||
@section('section')
|
||||
<div class="title">
|
||||
<h3 class="font-weight-bold d-inline-block">Instances</h3>
|
||||
<span class="float-right">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-light btn-sm dropdown-toggle font-weight-bold" type="button" id="filterDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-filter"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="filterDropdown">
|
||||
<a class="dropdown-item font-weight-light" href="{{route('admin.instances')}}?filter=unlisted">Show only Unlisted</a>
|
||||
<a class="dropdown-item font-weight-light" href="{{route('admin.instances')}}?filter=autocw">Show only Auto CW</a>
|
||||
<a class="dropdown-item font-weight-light" href="{{route('admin.instances')}}?filter=banned">Show only Banned</a>
|
||||
<a class="dropdown-item font-weight-light" href="{{route('admin.instances')}}">Show all</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
@if($instances->count() == 0 && request()->has('filter') == false)
|
||||
<div class="alert alert-warning mb-3">
|
||||
<p class="lead font-weight-bold mb-0">Warning</p>
|
||||
<p class="font-weight-lighter mb-0">No instances were found.</p>
|
||||
</div>
|
||||
<p class="font-weight-lighter">Do you want to scan and populate instances from Profiles and Statuses?</p>
|
||||
<p>
|
||||
<form method="post">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-primary py-1 font-weight-bold">Run Scan</button>
|
||||
</form>
|
||||
</p>
|
||||
@else
|
||||
<ul class="list-group">
|
||||
@foreach($instances as $instance)
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<p class="h4 font-weight-normal mb-1">
|
||||
{{$instance->domain}}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<a class="btn btn-outline-primary btn-sm py-0 font-weight-normal" href="{{$instance->getUrl()}}">Overview</a>
|
||||
<button class="btn btn-outline-secondary btn-sm py-0 font-weight-normal btn-action mr-3"
|
||||
data-instance-id="{{$instance->id}}"
|
||||
data-instance-domain="{{$instance->domain}}"
|
||||
data-instance-unlisted="{{$instance->unlisted}}"
|
||||
data-instance-autocw="{{$instance->auto_cw}}"
|
||||
data-instance-banned="{{$instance->banned}}"
|
||||
>Actions</button>
|
||||
@if($instance->unlisted)
|
||||
<i class="fas fa-minus-circle text-danger" data-toggle="tooltip" title="Unlisted from timelines"></i>
|
||||
@endif
|
||||
@if($instance->auto_cw)
|
||||
<i class="fas fa-eye-slash text-danger" data-toggle="tooltip" title="CW applied to all media"></i>
|
||||
@endif
|
||||
@if($instance->banned)
|
||||
<i class="fas fa-shield-alt text-danger" data-toggle="tooltip" title="Instance is banned"></i>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-inline-block pr-4">
|
||||
<p class="h4 font-weight-light text-center">{{$instance->profiles()->count()}}</p>
|
||||
<p class="mb-0 small font-weight-normal text-muted">Profiles</p>
|
||||
</div>
|
||||
<div class="d-inline-block pr-4">
|
||||
<p class="h4 font-weight-light text-center">{{$instance->statuses()->count()}}</p>
|
||||
<p class="mb-0 small font-weight-normal text-muted">Statuses</p>
|
||||
</div>
|
||||
<div class="d-inline-block pr-4">
|
||||
<p class="h4 font-weight-light text-center text-muted">{{$instance->reported()->count()}}</p>
|
||||
<p class="mb-0 small font-weight-normal text-muted">Reports</p>
|
||||
</div>
|
||||
<div class="d-inline-block">
|
||||
<p class="h4 font-weight-light text-center text-muted filesize" data-size="{{$instance->media()->sum('size')}}">0</p>
|
||||
<p class="mb-0 small font-weight-normal text-muted">Storage Used</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<div class="d-flex justify-content-center mt-5 small">
|
||||
{{$instances->links()}}
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('.filesize').each(function(k,v) {
|
||||
$(this).text(filesize(v.getAttribute('data-size')))
|
||||
});
|
||||
|
||||
$('.btn-action').on('click', function(e) {
|
||||
let id = this.getAttribute('data-instance-id');
|
||||
let instanceDomain = this.getAttribute('data-instance-domain');
|
||||
let text = 'Domain: ' + instanceDomain;
|
||||
let unlisted = this.getAttribute('data-instance-unlisted');
|
||||
let autocw = this.getAttribute('data-instance-autocw');
|
||||
let banned = this.getAttribute('data-instance-banned');
|
||||
swal({
|
||||
title: 'Instance Actions',
|
||||
text: text,
|
||||
icon: 'warning',
|
||||
buttons: {
|
||||
unlist: {
|
||||
text: unlisted == 0 ? "Unlist" : "Re-list",
|
||||
className: "bg-warning",
|
||||
value: "unlisted",
|
||||
},
|
||||
cw: {
|
||||
text: autocw == 0 ? "CW Media" : "Remove AutoCW",
|
||||
text: autocw == 0 ? "CW Media" : "Remove AutoCW",
|
||||
className: "bg-warning",
|
||||
value: "autocw",
|
||||
},
|
||||
ban: {
|
||||
text: banned == 0 ? "Ban" : "Unban",
|
||||
className: "bg-danger",
|
||||
value: "ban",
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((value) => {
|
||||
switch (value) {
|
||||
case "unlisted":
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: unlisted == 0 ?
|
||||
"Are you sure you want to unlist " + instanceDomain + " ?" :
|
||||
"Are you sure you want to remove the unlisted rule of " + instanceDomain + " ?",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
})
|
||||
.then((unlist) => {
|
||||
if (unlist) {
|
||||
axios.post('/i/admin/instances/edit/' + id, {
|
||||
action: 'unlist'
|
||||
}).then((res) => {
|
||||
swal("Domain action was successful! The page will now refresh.", {
|
||||
icon: "success",
|
||||
});
|
||||
setTimeout(function() {
|
||||
window.location.href = window.location.href;
|
||||
}, 5000);
|
||||
}).catch((err) => {
|
||||
swal("Something went wrong!", "Please try again later.", "error");
|
||||
})
|
||||
} else {
|
||||
swal("Action Cancelled", "You successfully cancelled this action.", "error");
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "autocw":
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: autocw == 0 ?
|
||||
"Are you sure you want to auto CW all media from " + instanceDomain + " ?" :
|
||||
"Are you sure you want to remove the auto cw rule for " + instanceDomain + " ?",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
axios.post('/i/admin/instances/edit/' + id, {
|
||||
action: 'autocw'
|
||||
}).then((res) => {
|
||||
swal("Domain action was successful! The page will now refresh.", {
|
||||
icon: "success",
|
||||
});
|
||||
setTimeout(function() {
|
||||
window.location.href = window.location.href;
|
||||
}, 5000);
|
||||
}).catch((err) => {
|
||||
swal("Something went wrong!", "Please try again later.", "error");
|
||||
})
|
||||
} else {
|
||||
swal("Action Cancelled", "You successfully cancelled this action.", "error");
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "ban":
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: autocw == 0 ?
|
||||
"Are you sure you want to ban " + instanceDomain + " ?" :
|
||||
"Are you sure you want unban " + instanceDomain + " ?",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
axios.post('/i/admin/instances/edit/' + id, {
|
||||
action: 'ban'
|
||||
}).then((res) => {
|
||||
swal("Domain action was successful! The page will now refresh.", {
|
||||
icon: "success",
|
||||
});
|
||||
setTimeout(function() {
|
||||
window.location.href = window.location.href;
|
||||
}, 5000);
|
||||
}).catch((err) => {
|
||||
swal("Something went wrong!", "Please try again later.", "error");
|
||||
})
|
||||
} else {
|
||||
swal("Action Cancelled", "You successfully cancelled this action.", "error");
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@endpush
|
113
resources/views/admin/instances/show.blade.php
Normal file
113
resources/views/admin/instances/show.blade.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
@extends('admin.partial.template')
|
||||
|
||||
@section('section')
|
||||
<div class="title">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h3 class="font-weight-bold mb-0">Instance Overview</h3>
|
||||
<p class="font-weight-lighter mb-0">domain: {{$instance->domain}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary btn-sm py-1" href="{{route('admin.instances')}}">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<p class="font-weight-lighter mb-0">unlisted: {{$instance->unlisted ? 'true' : 'false'}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-weight-lighter mb-0">CW media: {{$instance->auto_cw ? 'true' : 'false'}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-weight-lighter mb-0">banned: {{$instance->banned ? 'true' : 'false'}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body text-center">
|
||||
<p class="mb-0 font-weight-lighter display-4">
|
||||
{{$instance->profiles->count()}}
|
||||
</p>
|
||||
<p class="mb-0 text-muted">Profiles</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body text-center">
|
||||
<p class="mb-0 font-weight-lighter display-4">
|
||||
{{$instance->reports->count()}}
|
||||
</p>
|
||||
<p class="mb-0 text-muted">Reports</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body text-center">
|
||||
<p class="mb-0 font-weight-lighter display-4">
|
||||
{{$instance->statuses->count()}}
|
||||
</p>
|
||||
<p class="mb-0 text-muted">Statuses</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body text-center">
|
||||
<p class="mb-0 font-weight-lighter display-4 filesize" data-size="{{$instance->media()->sum('size')}}">
|
||||
0
|
||||
</p>
|
||||
<p class="mb-0 text-muted">Storage Used</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light h4 font-weight-lighter">
|
||||
Profiles
|
||||
<span class="float-right">
|
||||
<a class="btn btn-outline-secondary btn-sm py-0" href="#">View All</a>
|
||||
</span>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
@foreach($instance->profiles()->latest()->take(5)->get() as $profile)
|
||||
<li class="list-group-item">
|
||||
<a class="btn btn-outline-primary btn-block btn-sm" href="{{$profile->url()}}">{{$profile->emailUrl()}}</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light h4 font-weight-lighter">
|
||||
Statuses
|
||||
<span class="float-right">
|
||||
<a class="btn btn-outline-secondary btn-sm py-0" href="#">View All</a>
|
||||
</span>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
@foreach($instance->statuses()->latest()->take(5)->get() as $status)
|
||||
<li class="list-group-item">
|
||||
<a class="btn btn-outline-primary btn-block btn-sm" href="{{$status->url()}}">Status ID: {{$status->id}}</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('.filesize').each(function(k,v) {
|
||||
$(this).text(filesize(v.getAttribute('data-size')))
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
41
resources/views/admin/media/show.blade.php
Normal file
41
resources/views/admin/media/show.blade.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
@extends('admin.partial.template')
|
||||
|
||||
@section('section')
|
||||
<div class="title">
|
||||
<h3 class="font-weight-bold d-inline-block">Media</h3>
|
||||
<p class="font-weight-lighter mb-0">ID: {{$media->id}}</p>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<img class="card-img-top" src="{{$media->thumb()}}">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex justify-content-between">
|
||||
<div>
|
||||
<p class="mb-0 small">status id: <a href="{{$media->status->url()}}" class="font-weight-bold">{{$media->status_id}}</a></p>
|
||||
<p class="mb-0 small">username: <a href="{{$media->profile->url()}}" class="font-weight-bold">{{$media->profile->username}}</a></p>
|
||||
<p class="mb-0 small">size: <span class="filesize font-weight-bold" data-size="{{$media->size}}">0</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-0 small">mime: <span class="font-weight-bold">{{$media->mime}}</span></p>
|
||||
<p class="mb-0 small">content warning: <i class="fas {{$media->is_nsfw ? 'fa-check text-danger':'fa-times text-success'}}"></i></p>
|
||||
<p class="mb-0 small">
|
||||
remote media: <i class="fas {{$media->remote_media ? 'fa-check text-danger':'fa-times text-success'}}"></i></p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('.filesize').each(function(k,v) {
|
||||
$(this).text(filesize(v.getAttribute('data-size')))
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
|
@ -1,7 +1,7 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
|
||||
@yield('header')
|
||||
<div class="container">
|
||||
<div class="col-12 mt-5">
|
||||
<div class="card">
|
||||
|
|
|
@ -38,13 +38,14 @@
|
|||
<div class="square {{$status->firstMedia()->filter_class}}">
|
||||
@switch($status->viewType())
|
||||
@case('album')
|
||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6"><i class="fas fa-images fa-2x"></i></span>
|
||||
@case('photo:album')
|
||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6;text-shadow: 3px 3px 16px #272634;"><i class="fas fa-images fa-2x"></i></span>
|
||||
@break
|
||||
@case('video')
|
||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6"><i class="fas fa-video fa-2x"></i></span>
|
||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6;text-shadow: 3px 3px 16px #272634;"><i class="fas fa-video fa-2x"></i></span>
|
||||
@break
|
||||
@case('video-album')
|
||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6"><i class="fas fa-film fa-2x"></i></span>
|
||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6;text-shadow: 3px 3px 16px #272634;"><i class="fas fa-film fa-2x"></i></span>
|
||||
@break
|
||||
@endswitch
|
||||
<div class="square-content" style="background-image: url('{{$status->thumb()}}')">
|
||||
|
@ -109,7 +110,7 @@
|
|||
let elem = document.querySelector('.profile-timeline');
|
||||
let infScroll = new InfiniteScroll( elem, {
|
||||
path: '.pagination__next',
|
||||
append: '.profile-timeline',
|
||||
append: '.profile-timeline .row',
|
||||
status: '.page-load-status',
|
||||
history: false,
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<label class="custom-file-label" for="avatarInput">Select a profile photo</label>
|
||||
</div>
|
||||
<p><span class="small font-weight-bold">Must be a jpeg or png. Max avatar size: <span id="maxAvatarSize"></span></span></p>
|
||||
<div id="previewAvatar"></div>
|
||||
<p class="mb-0"><button type="submit" class="btn btn-primary px-4 py-0 font-weight-bold">Upload</button></p>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -130,5 +131,18 @@
|
|||
});
|
||||
|
||||
$('#maxAvatarSize').text(filesize({{config('pixelfed.max_avatar_size') * 1024}}, {round: 0}));
|
||||
|
||||
$('#avatarInput').on('change', function(e) {
|
||||
var file = document.getElementById('avatarInput').files[0];
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.addEventListener("load", function() {
|
||||
$('#previewAvatar').html('<img src="' + reader.result + '" class="rounded-circle box-shadow mb-3" width="100%" height="100%"/>');
|
||||
}, false);
|
||||
|
||||
if (file) {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</p>
|
||||
{{-- <hr>
|
||||
<hr>
|
||||
<p class="h5 text-muted font-weight-light" id="delete-your-account">Delete Your Account</p>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse1" role="button" aria-expanded="false" aria-controls="del-collapse1">
|
||||
|
@ -159,6 +159,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</p>
|
||||
@if(config('pixelfed.account_deletion'))
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse2" role="button" aria-expanded="false" aria-controls="del-collapse2">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
|
@ -166,9 +167,15 @@
|
|||
</a>
|
||||
<div class="collapse" id="del-collapse2">
|
||||
<div>
|
||||
@if(config('pixelfed.account_delete_after') == false)
|
||||
<div class="bg-light p-3 mb-4">
|
||||
<p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be permanently removed. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
|
||||
<p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be <b>permanently removed</b>. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="bg-light p-3 mb-4">
|
||||
<p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be <b>permanently removed</b> after {{config('pixelfed.account_delete_after')}} days. You can log in during that period to prevent your account from permanent deletion. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
|
||||
</div>
|
||||
@endif
|
||||
<p>After you delete your account, you can't sign up again with the same username on this instance or add that username to another account on this instance, and we can't reactivate deleted accounts.</p>
|
||||
<p>To permanently delete your account:</p>
|
||||
<ol class="font-weight-light">
|
||||
|
@ -178,5 +185,6 @@
|
|||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</p> --}}
|
||||
</p>
|
||||
@endif
|
||||
@endsection
|
|
@ -20,7 +20,7 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
|||
|
||||
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () {
|
||||
Route::get('/', 'SiteController@home')->name('timeline.personal');
|
||||
Route::post('/', 'StatusController@store')->middleware('throttle:500,1440');
|
||||
Route::post('/', 'StatusController@store');
|
||||
|
||||
Auth::routes();
|
||||
|
||||
|
@ -41,7 +41,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials');
|
||||
Route::post('avatar/update', 'ApiController@avatarUpdate');
|
||||
Route::get('likes', 'ApiController@hydrateLikes');
|
||||
Route::post('media', 'ApiController@uploadMedia')->middleware('throttle:500,1440');
|
||||
Route::post('media', 'ApiController@uploadMedia');
|
||||
Route::get('notifications', 'ApiController@notifications');
|
||||
Route::get('timelines/public', 'PublicApiController@publicTimelineApi');
|
||||
Route::get('timelines/home', 'PublicApiController@homeTimelineApi');
|
||||
|
@ -58,7 +58,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::group(['prefix' => 'local'], function () {
|
||||
Route::get('i/follow-suggestions', 'ApiController@followSuggestions');
|
||||
Route::post('i/more-comments', 'ApiController@loadMoreComments');
|
||||
Route::post('status/compose', 'InternalApiController@compose')->middleware('throttle:500,1440');
|
||||
Route::post('status/compose', 'InternalApiController@compose');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -67,20 +67,20 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::group(['prefix' => 'i'], function () {
|
||||
Route::redirect('/', '/');
|
||||
Route::get('compose', 'StatusController@compose')->name('compose');
|
||||
Route::post('comment', 'CommentController@store')->middleware('throttle:1000,1440');
|
||||
Route::post('delete', 'StatusController@delete')->middleware('throttle:1000,1440');
|
||||
Route::post('comment', 'CommentController@store');
|
||||
Route::post('delete', 'StatusController@delete');
|
||||
Route::post('mute', 'AccountController@mute');
|
||||
Route::post('block', 'AccountController@block');
|
||||
Route::post('like', 'LikeController@store')->middleware('throttle:1000,1440');
|
||||
Route::post('share', 'StatusController@storeShare')->middleware('throttle:1000,1440');
|
||||
Route::post('follow', 'FollowerController@store')->middleware('throttle:250,1440');
|
||||
Route::post('bookmark', 'BookmarkController@store')->middleware('throttle:250,1440');
|
||||
Route::post('like', 'LikeController@store');
|
||||
Route::post('share', 'StatusController@storeShare');
|
||||
Route::post('follow', 'FollowerController@store');
|
||||
Route::post('bookmark', 'BookmarkController@store');
|
||||
Route::get('lang/{locale}', 'SiteController@changeLocale');
|
||||
Route::get('restored', 'AccountController@accountRestored');
|
||||
|
||||
Route::get('verify-email', 'AccountController@verifyEmail');
|
||||
Route::post('verify-email', 'AccountController@sendVerifyEmail')->middleware('throttle:10,1440');
|
||||
Route::get('confirm-email/{userToken}/{randomToken}', 'AccountController@confirmVerifyEmail')->middleware('throttle:10,1440');
|
||||
Route::post('verify-email', 'AccountController@sendVerifyEmail');
|
||||
Route::get('confirm-email/{userToken}/{randomToken}', 'AccountController@confirmVerifyEmail');
|
||||
|
||||
Route::get('auth/sudo', 'AccountController@sudoMode');
|
||||
Route::post('auth/sudo', 'AccountController@sudoModeVerify');
|
||||
|
@ -92,7 +92,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
|
||||
Route::group(['prefix' => 'report'], function () {
|
||||
Route::get('/', 'ReportController@showForm')->name('report.form');
|
||||
Route::post('/', 'ReportController@formStore')->middleware('throttle:10,5');
|
||||
Route::post('/', 'ReportController@formStore');
|
||||
Route::get('not-interested', 'ReportController@notInterestedForm')->name('report.not-interested');
|
||||
Route::get('spam', 'ReportController@spamForm')->name('report.spam');
|
||||
Route::get('spam/comment', 'ReportController@spamCommentForm')->name('report.spam.comment');
|
||||
|
@ -118,19 +118,19 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::redirect('/', '/settings/home');
|
||||
Route::get('home', 'SettingsController@home')
|
||||
->name('settings');
|
||||
Route::post('home', 'SettingsController@homeUpdate')->middleware('throttle:250,1440');
|
||||
Route::post('home', 'SettingsController@homeUpdate');
|
||||
Route::get('avatar', 'SettingsController@avatar')->name('settings.avatar');
|
||||
Route::post('avatar', 'AvatarController@store');
|
||||
Route::get('password', 'SettingsController@password')->name('settings.password')->middleware('dangerzone');
|
||||
Route::post('password', 'SettingsController@passwordUpdate')->middleware(['throttle:2,1440','dangerzone']);
|
||||
Route::post('password', 'SettingsController@passwordUpdate')->middleware('dangerzone');
|
||||
Route::get('email', 'SettingsController@email')->name('settings.email');
|
||||
Route::get('notifications', 'SettingsController@notifications')->name('settings.notifications');
|
||||
Route::get('privacy', 'SettingsController@privacy')->name('settings.privacy');
|
||||
Route::post('privacy', 'SettingsController@privacyStore')->middleware('throttle:250,1440');
|
||||
Route::post('privacy', 'SettingsController@privacyStore');
|
||||
Route::get('privacy/muted-users', 'SettingsController@mutedUsers')->name('settings.privacy.muted-users');
|
||||
Route::post('privacy/muted-users', 'SettingsController@mutedUsersUpdate')->middleware('throttle:100,1440');
|
||||
Route::post('privacy/muted-users', 'SettingsController@mutedUsersUpdate');
|
||||
Route::get('privacy/blocked-users', 'SettingsController@blockedUsers')->name('settings.privacy.blocked-users');
|
||||
Route::post('privacy/blocked-users', 'SettingsController@blockedUsersUpdate')->middleware('throttle:100,1440');
|
||||
Route::post('privacy/blocked-users', 'SettingsController@blockedUsersUpdate');
|
||||
Route::get('privacy/blocked-instances', 'SettingsController@blockedInstances')->name('settings.privacy.blocked-instances');
|
||||
|
||||
// Todo: Release in 0.7.2
|
||||
|
|
168
tests/Unit/ActivityPub/Verb/AnnounceTest.php
Normal file
168
tests/Unit/ActivityPub/Verb/AnnounceTest.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\ActivityPub\Verb;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use App\Util\ActivityPub\Validator\Announce;
|
||||
|
||||
class AnnounceTest extends TestCase
|
||||
{
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->validAnnounce = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce",
|
||||
"actor" => "https://example.org/users/alice",
|
||||
"published" => "2018-12-31T23:59:59Z",
|
||||
"to" => [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc" => [
|
||||
"https://example.org/users/bob",
|
||||
"https://example.org/users/alice/followers"
|
||||
],
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
|
||||
$this->invalidAnnounce = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce2",
|
||||
"actor" => "https://example.org/users/alice",
|
||||
"published" => "2018-12-31T23:59:59Z",
|
||||
"to" => [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc" => [
|
||||
"https://example.org/users/bob",
|
||||
"https://example.org/users/alice/followers"
|
||||
],
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
|
||||
$this->invalidDate = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce",
|
||||
"actor" => "https://example.org/users/alice",
|
||||
"published" => "2018-12-31T23:59:59ZEZE",
|
||||
"to" => [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc" => [
|
||||
"https://example.org/users/bob",
|
||||
"https://example.org/users/alice/followers"
|
||||
],
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
|
||||
$this->contextMissing = [
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce",
|
||||
"actor" => "https://example.org/users/alice",
|
||||
"published" => "2018-12-31T23:59:59Z",
|
||||
"to" => [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc" => [
|
||||
"https://example.org/users/bob",
|
||||
"https://example.org/users/alice/followers"
|
||||
],
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
|
||||
$this->audienceMissing = [
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce",
|
||||
"actor" => "https://example.org/users/alice",
|
||||
"published" => "2018-12-31T23:59:59Z",
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
|
||||
$this->audienceMissing2 = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce",
|
||||
"actor" => "https://example.org/users/alice",
|
||||
"published" => "2018-12-31T23:59:59Z",
|
||||
"to" => null,
|
||||
"cc" => null,
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
|
||||
$this->invalidActor = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce",
|
||||
"actor" => "10000",
|
||||
"published" => "2018-12-31T23:59:59Z",
|
||||
"to" => [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc" => [
|
||||
"https://example.org/users/bob",
|
||||
"https://example.org/users/alice/followers"
|
||||
],
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
|
||||
$this->invalidActor2 = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
|
||||
"type" => "Announce",
|
||||
"published" => "2018-12-31T23:59:59Z",
|
||||
"to" => [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc" => [
|
||||
"https://example.org/users/bob",
|
||||
"https://example.org/users/alice/followers"
|
||||
],
|
||||
"object" => "https://example.org/p/bob/100000000000000",
|
||||
];
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function basic_accept()
|
||||
{
|
||||
$this->assertTrue(Announce::validate($this->validAnnounce));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function invalid_accept()
|
||||
{
|
||||
$this->assertFalse(Announce::validate($this->invalidAnnounce));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function invalid_date()
|
||||
{
|
||||
$this->assertFalse(Announce::validate($this->invalidDate));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function context_missing()
|
||||
{
|
||||
$this->assertFalse(Announce::validate($this->contextMissing));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function audience_missing()
|
||||
{
|
||||
$this->assertFalse(Announce::validate($this->audienceMissing));
|
||||
$this->assertFalse(Announce::validate($this->audienceMissing2));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function invalid_actor()
|
||||
{
|
||||
$this->assertFalse(Announce::validate($this->invalidActor));
|
||||
$this->assertFalse(Announce::validate($this->invalidActor2));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue