mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-02-03 18:30:47 +00:00
Merge branch 'pixelfed:dev' into main
This commit is contained in:
commit
f34e3eac1a
130 changed files with 6202 additions and 2196 deletions
58
CHANGELOG.md
58
CHANGELOG.md
|
@ -1,9 +1,38 @@
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.8...dev)
|
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.9...dev)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Resilient Media Storage ([#4665](https://github.com/pixelfed/pixelfed/pull/4665)) ([fb1deb6](https://github.com/pixelfed/pixelfed/commit/fb1deb6))
|
||||||
|
|
||||||
|
### Federation
|
||||||
|
- Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e))
|
||||||
|
- Update AP Helpers, consume actor `indexable` attribute ([fbdcdd9d](https://github.com/pixelfed/pixelfed/commit/fbdcdd9d))
|
||||||
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- Update FollowerService, add forget method to RelationshipService call to reduce load when mass purging ([347e4f59](https://github.com/pixelfed/pixelfed/commit/347e4f59))
|
||||||
|
- Update FollowServiceWarmCache, improve handling larger following/follower lists ([61a6d904](https://github.com/pixelfed/pixelfed/commit/61a6d904))
|
||||||
|
- Update StoryApiV1Controller, add viewers route to view story viewers ([941736ce](https://github.com/pixelfed/pixelfed/commit/941736ce))
|
||||||
|
- Update NotificationService, improve cache warming query ([2496386d](https://github.com/pixelfed/pixelfed/commit/2496386d))
|
||||||
|
- Update StatusService, hydrate accounts on request instead of caching them along with status objects ([223661ec](https://github.com/pixelfed/pixelfed/commit/223661ec))
|
||||||
|
- Update profile embed, fix resize ([dc23c21d](https://github.com/pixelfed/pixelfed/commit/dc23c21d))
|
||||||
|
- Update Status model, improve thumb logic ([d969a973](https://github.com/pixelfed/pixelfed/commit/d969a973))
|
||||||
|
- Update Status model, allow unlisted thumbnails ([1f0a45b7](https://github.com/pixelfed/pixelfed/commit/1f0a45b7))
|
||||||
|
- Update StatusTagsPipeline, fix object tags and slug normalization ([d295e605](https://github.com/pixelfed/pixelfed/commit/d295e605))
|
||||||
|
- Update Note and CreateNote transformers, include attachment blurhash, width and height ([ce1afe27](https://github.com/pixelfed/pixelfed/commit/ce1afe27))
|
||||||
|
- Update ap helpers, store media attachment width and height if present ([8c969191](https://github.com/pixelfed/pixelfed/commit/8c969191))
|
||||||
|
- Update Sign-in with Mastodon, allow usage when registrations are closed ([895dc4fa](https://github.com/pixelfed/pixelfed/commit/895dc4fa))
|
||||||
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
|
## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Import from Instagram ([#4466](https://github.com/pixelfed/pixelfed/pull/4466)) ([cf3078c5](https://github.com/pixelfed/pixelfed/commit/cf3078c5))
|
- Import from Instagram ([#4466](https://github.com/pixelfed/pixelfed/pull/4466)) ([cf3078c5](https://github.com/pixelfed/pixelfed/commit/cf3078c5))
|
||||||
|
- Sign-in with Mastodon ([#4545](https://github.com/pixelfed/pixelfed/pull/4545)) ([45b9404e](https://github.com/pixelfed/pixelfed/commit/45b9404e))
|
||||||
|
- Health check endpoint at /api/service/health-check ([ff58f970](https://github.com/pixelfed/pixelfed/commit/ff58f970))
|
||||||
|
- Reblogs in home feed ([#4563](https://github.com/pixelfed/pixelfed/pull/4563)) ([b86d47bf](https://github.com/pixelfed/pixelfed/commit/b86d47bf))
|
||||||
|
- Account Migrations ([#4578](https://github.com/pixelfed/pixelfed/pull/4578)) ([a9220e4e](https://github.com/pixelfed/pixelfed/commit/a9220e4e))
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
- Update Notifications.vue component, fix filtering logic to prevent endless spinner ([3df9b53f](https://github.com/pixelfed/pixelfed/commit/3df9b53f))
|
- Update Notifications.vue component, fix filtering logic to prevent endless spinner ([3df9b53f](https://github.com/pixelfed/pixelfed/commit/3df9b53f))
|
||||||
|
@ -26,7 +55,32 @@
|
||||||
- Update ComposeModal.vue, fix scroll issue and dont hide scrollbar ([2d959fb3](https://github.com/pixelfed/pixelfed/commit/2d959fb3))
|
- Update ComposeModal.vue, fix scroll issue and dont hide scrollbar ([2d959fb3](https://github.com/pixelfed/pixelfed/commit/2d959fb3))
|
||||||
- Update AccountImport, add select first 100 posts button ([625a76a5](https://github.com/pixelfed/pixelfed/commit/625a76a5))
|
- Update AccountImport, add select first 100 posts button ([625a76a5](https://github.com/pixelfed/pixelfed/commit/625a76a5))
|
||||||
- Update ApiV1Controller, add include_reblogs attribute to home timeline ([37fd0342](https://github.com/pixelfed/pixelfed/commit/37fd0342))
|
- Update ApiV1Controller, add include_reblogs attribute to home timeline ([37fd0342](https://github.com/pixelfed/pixelfed/commit/37fd0342))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- Update rate limits, fixes #4537 ([1cc6274a](https://github.com/pixelfed/pixelfed/commit/1cc6274a))
|
||||||
|
- Update Services, use zpopmin on predis ([4b2c66f5](https://github.com/pixelfed/pixelfed/commit/4b2c66f5))
|
||||||
|
- Update Inbox, allow storing Create->Note activities without any local followers, disabled by default ([9fa6b3f7](https://github.com/pixelfed/pixelfed/commit/9fa6b3f7))
|
||||||
|
- Update AP Helpers, preserve admin unlisted state before adding to NetworkTimelineService ([0704c7e0](https://github.com/pixelfed/pixelfed/commit/0704c7e0))
|
||||||
|
- Update SearchApiV2Service, improve resolve query logic to better handle remote posts/profiles and local posts/profiles ([c61d0b91](https://github.com/pixelfed/pixelfed/commit/c61d0b91))
|
||||||
|
- Update FollowPipeline, improve follower/following count calculation ([0b515767](https://github.com/pixelfed/pixelfed/commit/0b515767))
|
||||||
|
- Update TransformImports command, increment status_count on profile model ([ba7551d8](https://github.com/pixelfed/pixelfed/commit/ba7551d8))
|
||||||
|
- Update AP Helpers, improve url validation and add optional dns verification, disabled by default ([2bef3e41](https://github.com/pixelfed/pixelfed/commit/2bef3e41))
|
||||||
|
- Update admin users blade view, show last_active_at and other info ([e0b48b29](https://github.com/pixelfed/pixelfed/commit/e0b48b29))
|
||||||
|
- Update MediaStorageService, improve head header handling ([3590adbd](https://github.com/pixelfed/pixelfed/commit/3590adbd))
|
||||||
|
- Update admin user view, improve previews ([ff2c16fe](https://github.com/pixelfed/pixelfed/commit/ff2c16fe))
|
||||||
|
- Update FanoutDeletePipeline, fix AP object ([0d802c31](https://github.com/pixelfed/pixelfed/commit/0d802c31))
|
||||||
|
- Update Remote Auth feature, fix custom domain bug and enforce banned domains ([acabf603](https://github.com/pixelfed/pixelfed/commit/acabf603))
|
||||||
|
- Update StatusService, reduce cache ttl from 7 days to 6 hours ([59b64378](https://github.com/pixelfed/pixelfed/commit/59b64378))
|
||||||
|
- Update ProfileController, allow albums in atom feed. Closes #4561. Fixes #4526 ([1c105a6c](https://github.com/pixelfed/pixelfed/commit/1c105a6c))
|
||||||
|
- Update admin users view, fix website value. Closes #4557 ([c469d475](https://github.com/pixelfed/pixelfed/commit/c469d475))
|
||||||
|
- Update StatusStatelessTransformer, allow unlisted reblogs ([1c13b518](https://github.com/pixelfed/pixelfed/commit/1c13b518))
|
||||||
|
- Update ApiV1Controller, hydrate reblog state in home timeline ([13bdaa2e](https://github.com/pixelfed/pixelfed/commit/13bdaa2e))
|
||||||
|
- Update Timeline component, improve reblog support ([29de91e5](https://github.com/pixelfed/pixelfed/commit/29de91e5))
|
||||||
|
- Update timeline settings, add photo reblogs only option ([e2705b9a](https://github.com/pixelfed/pixelfed/commit/e2705b9a))
|
||||||
|
- Update PostContent, add text cw warning ([911504fa](https://github.com/pixelfed/pixelfed/commit/911504fa))
|
||||||
|
- Update ActivityPubFetchService, add validateUrl parameter to bypass url validation to fetch content from blocked instances ([3d1b6516](https://github.com/pixelfed/pixelfed/commit/3d1b6516))
|
||||||
|
- Update RemoteStatusDelete pipeline ([71e92261](https://github.com/pixelfed/pixelfed/commit/71e92261))
|
||||||
|
- Update RemoteStatusDelete pipeline ([fab8f25e](https://github.com/pixelfed/pixelfed/commit/fab8f25e))
|
||||||
|
- Update RemoteStatusPipeline, fix reply check ([618b6727](https://github.com/pixelfed/pixelfed/commit/618b6727))
|
||||||
|
- Update ApiV1Controller, add bookmarked to timeline entities ([ca746717](https://github.com/pixelfed/pixelfed/commit/ca746717))
|
||||||
|
|
||||||
## [v0.11.8 (2023-05-29)](https://github.com/pixelfed/pixelfed/compare/v0.11.7...v0.11.8)
|
## [v0.11.8 (2023-05-29)](https://github.com/pixelfed/pixelfed/compare/v0.11.7...v0.11.8)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use App\Media;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use Storage;
|
use Storage;
|
||||||
|
use App\Services\AccountService;
|
||||||
use App\Services\MediaPathService;
|
use App\Services\MediaPathService;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Util\Lexer\Autolink;
|
use App\Util\Lexer\Autolink;
|
||||||
|
@ -38,7 +39,7 @@ class TransformImports extends Command
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ips = ImportPost::whereNull('status_id')->where('skip_missing_media', '!=', true)->take(200)->get();
|
$ips = ImportPost::whereNull('status_id')->where('skip_missing_media', '!=', true)->take(500)->get();
|
||||||
|
|
||||||
if(!$ips->count()) {
|
if(!$ips->count()) {
|
||||||
return;
|
return;
|
||||||
|
@ -135,6 +136,11 @@ class TransformImports extends Command
|
||||||
$ip->creation_id = $idk['incr'];
|
$ip->creation_id = $idk['incr'];
|
||||||
$ip->save();
|
$ip->save();
|
||||||
|
|
||||||
|
$profile->status_count = $profile->status_count + 1;
|
||||||
|
$profile->save();
|
||||||
|
|
||||||
|
AccountService::del($profile->id);
|
||||||
|
|
||||||
ImportService::clearAttempts($profile->id);
|
ImportService::clearAttempts($profile->id);
|
||||||
ImportService::getPostCount($profile->id, true);
|
ImportService::getPostCount($profile->id, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ use App\Services\StoryService;
|
||||||
use App\Services\ModLogService;
|
use App\Services\ModLogService;
|
||||||
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
||||||
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||||
use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;
|
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
use App\Http\Resources\AdminReport;
|
use App\Http\Resources\AdminReport;
|
||||||
use App\Http\Resources\AdminSpamReport;
|
use App\Http\Resources\AdminSpamReport;
|
||||||
use App\Services\NotificationService;
|
use App\Services\NotificationService;
|
||||||
|
@ -643,7 +643,7 @@ trait AdminReportController
|
||||||
$q->whereNull('admin_seen') :
|
$q->whereNull('admin_seen') :
|
||||||
$q->whereNotNull('admin_seen');
|
$q->whereNotNull('admin_seen');
|
||||||
})
|
})
|
||||||
->groupBy(['object_id', 'object_type'])
|
->groupBy(['id', 'object_id', 'object_type'])
|
||||||
->cursorPaginate(6)
|
->cursorPaginate(6)
|
||||||
->withQueryString()
|
->withQueryString()
|
||||||
);
|
);
|
||||||
|
@ -1049,7 +1049,7 @@ trait AdminReportController
|
||||||
StatusDelete::dispatch($status)->onQueue('high');
|
StatusDelete::dispatch($status)->onQueue('high');
|
||||||
} else {
|
} else {
|
||||||
NetworkTimelineService::del($status->id);
|
NetworkTimelineService::del($status->id);
|
||||||
DeleteRemoteStatusPipeline::dispatch($status)->onQueue('high');
|
RemoteStatusDelete::dispatch($status)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
Report::whereObjectId($report->object_id)
|
Report::whereObjectId($report->object_id)
|
||||||
|
|
122
app/Http/Controllers/AdminShadowFilterController.php
Normal file
122
app/Http/Controllers/AdminShadowFilterController.php
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\AdminShadowFilter;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\AdminShadowFilterService;
|
||||||
|
|
||||||
|
class AdminShadowFilterController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware(['auth','admin']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function home(Request $request)
|
||||||
|
{
|
||||||
|
$filter = $request->input('filter');
|
||||||
|
$searchQuery = $request->input('q');
|
||||||
|
$filters = AdminShadowFilter::when($filter, function($q, $filter) {
|
||||||
|
if($filter == 'all') {
|
||||||
|
return $q;
|
||||||
|
} else if($filter == 'inactive') {
|
||||||
|
return $q->whereActive(false);
|
||||||
|
} else {
|
||||||
|
return $q;
|
||||||
|
}
|
||||||
|
}, function($q, $filter) {
|
||||||
|
return $q->whereActive(true);
|
||||||
|
})
|
||||||
|
->when($searchQuery, function($q, $searchQuery) {
|
||||||
|
$ids = Profile::where('username', 'like', '%' . $searchQuery . '%')
|
||||||
|
->limit(100)
|
||||||
|
->pluck('id')
|
||||||
|
->toArray();
|
||||||
|
return $q->where('item_type', 'App\Profile')->whereIn('item_id', $ids);
|
||||||
|
})
|
||||||
|
->latest()
|
||||||
|
->paginate(10)
|
||||||
|
->withQueryString();
|
||||||
|
|
||||||
|
return view('admin.asf.home', compact('filters'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request)
|
||||||
|
{
|
||||||
|
return view('admin.asf.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(Request $request, $id)
|
||||||
|
{
|
||||||
|
$filter = AdminShadowFilter::findOrFail($id);
|
||||||
|
$profile = AccountService::get($filter->item_id);
|
||||||
|
return view('admin.asf.edit', compact('filter', 'profile'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'username' => 'required',
|
||||||
|
'active' => 'sometimes',
|
||||||
|
'note' => 'sometimes',
|
||||||
|
'hide_from_public_feeds' => 'sometimes'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = Profile::whereUsername($request->input('username'))->first();
|
||||||
|
|
||||||
|
if(!$profile) {
|
||||||
|
return back()->withErrors(['Invalid account']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($profile->user && $profile->user->is_admin) {
|
||||||
|
return back()->withErrors(['Cannot filter an admin account']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$active = $request->has('active') && $request->has('hide_from_public_feeds');
|
||||||
|
|
||||||
|
AdminShadowFilter::updateOrCreate([
|
||||||
|
'item_id' => $profile->id,
|
||||||
|
'item_type' => get_class($profile)
|
||||||
|
], [
|
||||||
|
'is_local' => $profile->domain === null,
|
||||||
|
'note' => $request->input('note'),
|
||||||
|
'hide_from_public_feeds' => $request->has('hide_from_public_feeds'),
|
||||||
|
'admin_id' => $request->user()->profile_id,
|
||||||
|
'active' => $active
|
||||||
|
]);
|
||||||
|
|
||||||
|
AdminShadowFilterService::refresh();
|
||||||
|
|
||||||
|
return redirect('/i/admin/asf/home');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeEdit(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'active' => 'sometimes',
|
||||||
|
'note' => 'sometimes',
|
||||||
|
'hide_from_public_feeds' => 'sometimes'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$filter = AdminShadowFilter::findOrFail($id);
|
||||||
|
|
||||||
|
$profile = Profile::findOrFail($filter->item_id);
|
||||||
|
|
||||||
|
if($profile->user && $profile->user->is_admin) {
|
||||||
|
return back()->withErrors(['Cannot filter an admin account']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$active = $request->has('active');
|
||||||
|
$filter->active = $active;
|
||||||
|
$filter->hide_from_public_feeds = $request->has('hide_from_public_feeds');
|
||||||
|
$filter->note = $request->input('note');
|
||||||
|
$filter->save();
|
||||||
|
|
||||||
|
AdminShadowFilterService::refresh();
|
||||||
|
|
||||||
|
return redirect('/i/admin/asf/home');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1621,7 +1621,7 @@ class ApiV1Controller extends Controller
|
||||||
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
||||||
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
||||||
|
|
||||||
return $dailyLimit >= 250;
|
return $dailyLimit >= 1250;
|
||||||
});
|
});
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
|
||||||
|
@ -1826,7 +1826,7 @@ class ApiV1Controller extends Controller
|
||||||
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
||||||
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
||||||
|
|
||||||
return $dailyLimit >= 250;
|
return $dailyLimit >= 1250;
|
||||||
});
|
});
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
|
||||||
|
@ -2193,12 +2193,22 @@ class ApiV1Controller extends Controller
|
||||||
if($pid) {
|
if($pid) {
|
||||||
$status['favourited'] = (bool) LikeService::liked($pid, $s['id']);
|
$status['favourited'] = (bool) LikeService::liked($pid, $s['id']);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($pid, $status['id']);
|
$status['reblogged'] = (bool) ReblogService::get($pid, $status['id']);
|
||||||
|
$status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']);
|
||||||
}
|
}
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function($status) {
|
->filter(function($status) {
|
||||||
return $status && isset($status['account']);
|
return $status && isset($status['account']);
|
||||||
})
|
})
|
||||||
|
->map(function($status) use($pid) {
|
||||||
|
if(!empty($status['reblog'])) {
|
||||||
|
$status['reblog']['favourited'] = (bool) LikeService::liked($pid, $status['reblog']['id']);
|
||||||
|
$status['reblog']['reblogged'] = (bool) ReblogService::get($pid, $status['reblog']['id']);
|
||||||
|
$status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
})
|
||||||
->take($limit)
|
->take($limit)
|
||||||
->values();
|
->values();
|
||||||
} else {
|
} else {
|
||||||
|
@ -2236,12 +2246,22 @@ class ApiV1Controller extends Controller
|
||||||
if($pid) {
|
if($pid) {
|
||||||
$status['favourited'] = (bool) LikeService::liked($pid, $s['id']);
|
$status['favourited'] = (bool) LikeService::liked($pid, $s['id']);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($pid, $status['id']);
|
$status['reblogged'] = (bool) ReblogService::get($pid, $status['id']);
|
||||||
|
$status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']);
|
||||||
}
|
}
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function($status) {
|
->filter(function($status) {
|
||||||
return $status && isset($status['account']);
|
return $status && isset($status['account']);
|
||||||
})
|
})
|
||||||
|
->map(function($status) use($pid) {
|
||||||
|
if(!empty($status['reblog'])) {
|
||||||
|
$status['reblog']['favourited'] = (bool) LikeService::liked($pid, $status['reblog']['id']);
|
||||||
|
$status['reblog']['reblogged'] = (bool) ReblogService::get($pid, $status['reblog']['id']);
|
||||||
|
$status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
})
|
||||||
->take($limit)
|
->take($limit)
|
||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
|
@ -2362,6 +2382,7 @@ class ApiV1Controller extends Controller
|
||||||
if($user) {
|
if($user) {
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $status['id']);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $status['id']);
|
||||||
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $status['id']);
|
||||||
}
|
}
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
|
@ -2838,7 +2859,7 @@ class ApiV1Controller extends Controller
|
||||||
->where('created_at', '>', now()->subDays(1))
|
->where('created_at', '>', now()->subDays(1))
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
return $dailyLimit >= 100;
|
return $dailyLimit >= 1000;
|
||||||
});
|
});
|
||||||
|
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
@ -3599,8 +3620,8 @@ class ApiV1Controller extends Controller
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$home = $request->input('home.last_read_id');
|
$home = $request->input('home[last_read_id]');
|
||||||
$notifications = $request->input('notifications.last_read_id');
|
$notifications = $request->input('notifications[last_read_id]');
|
||||||
|
|
||||||
if($home) {
|
if($home) {
|
||||||
return $this->json(MarkerService::set($pid, 'home', $home));
|
return $this->json(MarkerService::set($pid, 'home', $home));
|
||||||
|
|
|
@ -17,6 +17,7 @@ use App\Report;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\StatusArchived;
|
use App\StatusArchived;
|
||||||
use App\User;
|
use App\User;
|
||||||
|
use App\UserSetting;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
use App\Services\ProfileStatusService;
|
use App\Services\ProfileStatusService;
|
||||||
|
@ -33,6 +34,7 @@ use App\Mail\PasswordChange;
|
||||||
use App\Mail\ConfirmAppEmail;
|
use App\Mail\ConfirmAppEmail;
|
||||||
use App\Http\Resources\StatusStateless;
|
use App\Http\Resources\StatusStateless;
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail;
|
use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail;
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
|
||||||
|
@ -837,7 +839,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
Cache::forget('profile:embed:' . $status->profile_id);
|
Cache::forget('profile:embed:' . $status->profile_id);
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
$status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -845,4 +847,41 @@ class ApiV1Dot1Controller extends Controller
|
||||||
|
|
||||||
return StatusService::get($status->id, false);
|
return StatusService::get($status->id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWebSettings(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 403);
|
||||||
|
$uid = $request->user()->id;
|
||||||
|
$settings = UserSetting::firstOrCreate([
|
||||||
|
'user_id' => $uid
|
||||||
|
]);
|
||||||
|
if(!$settings->other) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $settings->other;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWebSettings(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 403);
|
||||||
|
$this->validate($request, [
|
||||||
|
'field' => 'required|in:enable_reblogs,hide_reblog_banner',
|
||||||
|
'value' => 'required'
|
||||||
|
]);
|
||||||
|
$field = $request->input('field');
|
||||||
|
$value = $request->input('value');
|
||||||
|
$settings = UserSetting::firstOrCreate([
|
||||||
|
'user_id' => $request->user()->id
|
||||||
|
]);
|
||||||
|
if(!$settings->other) {
|
||||||
|
$other = [];
|
||||||
|
} else {
|
||||||
|
$other = $settings->other;
|
||||||
|
}
|
||||||
|
$other[$field] = $value;
|
||||||
|
$settings->other = $other;
|
||||||
|
$settings->save();
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ class ApiV2Controller extends Controller
|
||||||
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
||||||
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
||||||
|
|
||||||
return $dailyLimit >= 250;
|
return $dailyLimit >= 1250;
|
||||||
});
|
});
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ class ComposeController extends Controller
|
||||||
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
||||||
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
||||||
|
|
||||||
return $dailyLimit >= 250;
|
return $dailyLimit >= 1250;
|
||||||
});
|
});
|
||||||
|
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
@ -190,7 +190,7 @@ class ComposeController extends Controller
|
||||||
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
||||||
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
||||||
|
|
||||||
return $dailyLimit >= 500;
|
return $dailyLimit >= 1500;
|
||||||
});
|
});
|
||||||
|
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
@ -415,7 +415,7 @@ class ComposeController extends Controller
|
||||||
$results = Profile::select('id','domain','username')
|
$results = Profile::select('id','domain','username')
|
||||||
->whereNotIn('id', $blocked)
|
->whereNotIn('id', $blocked)
|
||||||
->where('username','like','%'.$q.'%')
|
->where('username','like','%'.$q.'%')
|
||||||
->groupBy('domain')
|
->groupBy('id', 'domain')
|
||||||
->limit(15)
|
->limit(15)
|
||||||
->get()
|
->get()
|
||||||
->map(function($profile) {
|
->map(function($profile) {
|
||||||
|
@ -499,7 +499,7 @@ class ComposeController extends Controller
|
||||||
->where('created_at', '>', now()->subDays(1))
|
->where('created_at', '>', now()->subDays(1))
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
return $dailyLimit >= 100;
|
return $dailyLimit >= 1000;
|
||||||
});
|
});
|
||||||
|
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
|
16
app/Http/Controllers/HealthCheckController.php
Normal file
16
app/Http/Controllers/HealthCheckController.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class HealthCheckController extends Controller
|
||||||
|
{
|
||||||
|
public function get(Request $request)
|
||||||
|
{
|
||||||
|
return response('OK')->withHeaders([
|
||||||
|
'Content-Type' => 'text/plain',
|
||||||
|
'Cache-Control' => 'max-age=0, must-revalidate, no-cache, no-store'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
64
app/Http/Controllers/ProfileAliasController.php
Normal file
64
app/Http/Controllers/ProfileAliasController.php
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Util\Lexer\Nickname;
|
||||||
|
use App\Util\Webfinger\WebfingerUrl;
|
||||||
|
use App\Models\ProfileAlias;
|
||||||
|
use App\Services\WebfingerService;
|
||||||
|
|
||||||
|
class ProfileAliasController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$aliases = $request->user()->profile->aliases;
|
||||||
|
return view('settings.aliases.index', compact('aliases'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'acct' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$acct = $request->input('acct');
|
||||||
|
|
||||||
|
if($request->user()->profile->aliases->count() >= 3) {
|
||||||
|
return back()->with('error', 'You can only add 3 account aliases.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$webfingerService = WebfingerService::lookup($acct);
|
||||||
|
if(!$webfingerService || !isset($webfingerService['url'])) {
|
||||||
|
return back()->with('error', 'Invalid account, cannot add alias at this time.');
|
||||||
|
}
|
||||||
|
$alias = new ProfileAlias;
|
||||||
|
$alias->profile_id = $request->user()->profile_id;
|
||||||
|
$alias->acct = $acct;
|
||||||
|
$alias->uri = $webfingerService['url'];
|
||||||
|
$alias->save();
|
||||||
|
|
||||||
|
return back()->with('status', 'Successfully added alias!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'acct' => 'required',
|
||||||
|
'id' => 'required|exists:profile_aliases'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$alias = ProfileAlias::where('profile_id', $request->user()->profile_id)
|
||||||
|
->where('acct', $request->input('acct'))
|
||||||
|
->findOrFail($request->input('id'));
|
||||||
|
|
||||||
|
$alias->delete();
|
||||||
|
|
||||||
|
return back()->with('status', 'Successfully deleted alias!');
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ use App\Follower;
|
||||||
use App\FollowRequest;
|
use App\FollowRequest;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Story;
|
use App\Story;
|
||||||
|
use App\Status;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\UserSetting;
|
use App\UserSetting;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
|
@ -253,15 +254,14 @@ class ProfileController extends Controller
|
||||||
abort_if(!$enabled, 404);
|
abort_if(!$enabled, 404);
|
||||||
|
|
||||||
$data = Cache::remember('pf:atom:user-feed:by-id:' . $profile['id'], 900, function() use($pid, $profile) {
|
$data = Cache::remember('pf:atom:user-feed:by-id:' . $profile['id'], 900, function() use($pid, $profile) {
|
||||||
$items = DB::table('statuses')
|
$items = Status::whereProfileId($pid)
|
||||||
->whereProfileId($pid)
|
->whereScope('public')
|
||||||
->whereVisibility('public')
|
->whereIn('type', ['photo', 'photo:album'])
|
||||||
->whereType('photo')
|
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->take(10)
|
->take(10)
|
||||||
->get()
|
->get()
|
||||||
->map(function($status) {
|
->map(function($status) {
|
||||||
return StatusService::get($status->id);
|
return StatusService::get($status->id, true);
|
||||||
})
|
})
|
||||||
->filter(function($status) {
|
->filter(function($status) {
|
||||||
return $status &&
|
return $status &&
|
||||||
|
|
718
app/Http/Controllers/RemoteAuthController.php
Normal file
718
app/Http/Controllers/RemoteAuthController.php
Normal file
|
@ -0,0 +1,718 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\Account\RemoteAuthService;
|
||||||
|
use App\Models\RemoteAuth;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Instance;
|
||||||
|
use App\User;
|
||||||
|
use Purify;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Auth\Events\Registered;
|
||||||
|
use App\Util\Lexer\RestrictedNames;
|
||||||
|
use App\Services\EmailService;
|
||||||
|
use App\Services\MediaStorageService;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class RemoteAuthController extends Controller
|
||||||
|
{
|
||||||
|
public function start(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
if($request->user()) {
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
return view('auth.remote.start');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startRedirect(Request $request)
|
||||||
|
{
|
||||||
|
return redirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthDomains(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
|
||||||
|
if(config('remote-auth.mastodon.domains.only_custom')) {
|
||||||
|
$res = config('remote-auth.mastodon.domains.custom');
|
||||||
|
if(!$res || !strlen($res)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$res = explode(',', $res);
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( config('remote-auth.mastodon.domains.custom') &&
|
||||||
|
!config('remote-auth.mastodon.domains.only_default') &&
|
||||||
|
strlen(config('remote-auth.mastodon.domains.custom')) > 3 &&
|
||||||
|
strpos(config('remote-auth.mastodon.domains.custom'), '.') > -1
|
||||||
|
) {
|
||||||
|
$res = config('remote-auth.mastodon.domains.custom');
|
||||||
|
if(!$res || !strlen($res)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$res = explode(',', $res);
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = config('remote-auth.mastodon.domains.default');
|
||||||
|
$res = explode(',', $res);
|
||||||
|
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function redirect(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
|
||||||
|
$this->validate($request, ['domain' => 'required']);
|
||||||
|
|
||||||
|
$domain = $request->input('domain');
|
||||||
|
|
||||||
|
if(str_starts_with(strtolower($domain), 'http')) {
|
||||||
|
$res = [
|
||||||
|
'domain' => $domain,
|
||||||
|
'ready' => false,
|
||||||
|
'action' => 'incompatible_domain'
|
||||||
|
];
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
$validateInstance = Helpers::validateUrl('https://' . $domain . '/?block-check=' . time());
|
||||||
|
|
||||||
|
if(!$validateInstance) {
|
||||||
|
$res = [
|
||||||
|
'domain' => $domain,
|
||||||
|
'ready' => false,
|
||||||
|
'action' => 'blocked_domain'
|
||||||
|
];
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
$compatible = RemoteAuthService::isDomainCompatible($domain);
|
||||||
|
|
||||||
|
if(!$compatible) {
|
||||||
|
$res = [
|
||||||
|
'domain' => $domain,
|
||||||
|
'ready' => false,
|
||||||
|
'action' => 'incompatible_domain'
|
||||||
|
];
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config('remote-auth.mastodon.domains.only_default')) {
|
||||||
|
$defaultDomains = explode(',', config('remote-auth.mastodon.domains.default'));
|
||||||
|
if(!in_array($domain, $defaultDomains)) {
|
||||||
|
$res = [
|
||||||
|
'domain' => $domain,
|
||||||
|
'ready' => false,
|
||||||
|
'action' => 'incompatible_domain'
|
||||||
|
];
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config('remote-auth.mastodon.domains.only_custom') && config('remote-auth.mastodon.domains.custom')) {
|
||||||
|
$customDomains = explode(',', config('remote-auth.mastodon.domains.custom'));
|
||||||
|
if(!in_array($domain, $customDomains)) {
|
||||||
|
$res = [
|
||||||
|
'domain' => $domain,
|
||||||
|
'ready' => false,
|
||||||
|
'action' => 'incompatible_domain'
|
||||||
|
];
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = RemoteAuthService::getMastodonClient($domain);
|
||||||
|
|
||||||
|
abort_unless($client, 422, 'Invalid mastodon client');
|
||||||
|
|
||||||
|
$request->session()->put('state', $state = Str::random(40));
|
||||||
|
$request->session()->put('oauth_domain', $domain);
|
||||||
|
|
||||||
|
$query = http_build_query([
|
||||||
|
'client_id' => $client->client_id,
|
||||||
|
'redirect_uri' => $client->redirect_uri,
|
||||||
|
'response_type' => 'code',
|
||||||
|
'scope' => 'read',
|
||||||
|
'state' => $state,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request->session()->put('oauth_redirect_to', 'https://' . $domain . '/oauth/authorize?' . $query);
|
||||||
|
|
||||||
|
$dsh = Str::random(17);
|
||||||
|
$res = [
|
||||||
|
'domain' => $domain,
|
||||||
|
'ready' => true,
|
||||||
|
'dsh' => $dsh
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function preflight(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
|
||||||
|
if(!$request->filled('d') || !$request->filled('dsh') || !$request->session()->exists('oauth_redirect_to')) {
|
||||||
|
return redirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->away($request->session()->pull('oauth_redirect_to'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleCallback(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
|
||||||
|
$domain = $request->session()->get('oauth_domain');
|
||||||
|
|
||||||
|
if($request->filled('code')) {
|
||||||
|
$code = $request->input('code');
|
||||||
|
$state = $request->session()->pull('state');
|
||||||
|
|
||||||
|
throw_unless(
|
||||||
|
strlen($state) > 0 && $state === $request->state,
|
||||||
|
InvalidArgumentException::class,
|
||||||
|
'Invalid state value.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$res = RemoteAuthService::getToken($domain, $code);
|
||||||
|
|
||||||
|
if(!$res || !isset($res['access_token'])) {
|
||||||
|
$request->session()->regenerate();
|
||||||
|
return redirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->session()->put('oauth_remote_session_token', $res['access_token']);
|
||||||
|
return redirect('/auth/mastodon/getting-started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onboarding(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
if($request->user()) {
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
return view('auth.remote.onboarding');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sessionCheck(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_if($request->user(), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
|
||||||
|
$domain = $request->session()->get('oauth_domain');
|
||||||
|
$token = $request->session()->get('oauth_remote_session_token');
|
||||||
|
|
||||||
|
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
||||||
|
|
||||||
|
abort_if(!$res || !isset($res['acct']), 403, 'Invalid credentials');
|
||||||
|
|
||||||
|
$webfinger = strtolower('@' . $res['acct'] . '@' . $domain);
|
||||||
|
$request->session()->put('oauth_masto_webfinger', $webfinger);
|
||||||
|
|
||||||
|
if(config('remote-auth.mastodon.max_uses.enabled')) {
|
||||||
|
$limit = config('remote-auth.mastodon.max_uses.limit');
|
||||||
|
$uses = RemoteAuthService::lookupWebfingerUses($webfinger);
|
||||||
|
if($uses >= $limit) {
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'msg' => 'Success!',
|
||||||
|
'action' => 'max_uses_reached'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = RemoteAuth::whereDomain($domain)->where('webfinger', $webfinger)->whereNotNull('user_id')->first();
|
||||||
|
if($exists && $exists->user_id) {
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'msg' => 'Success!',
|
||||||
|
'action' => 'redirect_existing_user'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'msg' => 'Success!',
|
||||||
|
'action' => 'onboard'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sessionGetMastodonData(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_if($request->user(), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
|
||||||
|
$domain = $request->session()->get('oauth_domain');
|
||||||
|
$token = $request->session()->get('oauth_remote_session_token');
|
||||||
|
|
||||||
|
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
||||||
|
$res['_webfinger'] = strtolower('@' . $res['acct'] . '@' . $domain);
|
||||||
|
$res['_domain'] = strtolower($domain);
|
||||||
|
$request->session()->put('oauth_remasto_id', $res['id']);
|
||||||
|
|
||||||
|
$ra = RemoteAuth::updateOrCreate([
|
||||||
|
'domain' => $domain,
|
||||||
|
'webfinger' => $res['_webfinger'],
|
||||||
|
], [
|
||||||
|
'software' => 'mastodon',
|
||||||
|
'ip_address' => $request->ip(),
|
||||||
|
'bearer_token' => $token,
|
||||||
|
'verify_credentials' => $res,
|
||||||
|
'last_verify_credentials_at' => now(),
|
||||||
|
'last_successful_login_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request->session()->put('oauth_masto_raid', $ra->id);
|
||||||
|
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sessionValidateUsername(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_if($request->user(), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'username' => [
|
||||||
|
'required',
|
||||||
|
'min:2',
|
||||||
|
'max:15',
|
||||||
|
function ($attribute, $value, $fail) {
|
||||||
|
$dash = substr_count($value, '-');
|
||||||
|
$underscore = substr_count($value, '_');
|
||||||
|
$period = substr_count($value, '.');
|
||||||
|
|
||||||
|
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||||
|
return $fail('Username is invalid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(($dash + $underscore + $period) > 1) {
|
||||||
|
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctype_alnum($value[0])) {
|
||||||
|
return $fail('Username is invalid. Must start with a letter or number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||||
|
return $fail('Username is invalid. Must end with a letter or number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$val = str_replace(['_', '.', '-'], '', $value);
|
||||||
|
if(!ctype_alnum($val)) {
|
||||||
|
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||||
|
}
|
||||||
|
|
||||||
|
$restricted = RestrictedNames::get();
|
||||||
|
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||||
|
return $fail('Username cannot be used.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$username = strtolower($request->input('username'));
|
||||||
|
|
||||||
|
$exists = User::where('username', $username)->exists();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'username' => $username,
|
||||||
|
'exists' => $exists
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sessionValidateEmail(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_if($request->user(), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'email:strict,filter_unicode,dns,spoof',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$email = $request->input('email');
|
||||||
|
$banned = EmailService::isBanned($email);
|
||||||
|
$exists = User::where('email', $email)->exists();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'email' => $email,
|
||||||
|
'exists' => $exists,
|
||||||
|
'banned' => $banned
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sessionGetMastodonFollowers(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
||||||
|
|
||||||
|
$domain = $request->session()->get('oauth_domain');
|
||||||
|
$token = $request->session()->get('oauth_remote_session_token');
|
||||||
|
$id = $request->session()->get('oauth_remasto_id');
|
||||||
|
|
||||||
|
$res = RemoteAuthService::getFollowing($domain, $token, $id);
|
||||||
|
|
||||||
|
if(!$res) {
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'following' => []
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = collect($res)->filter(fn($acct) => Helpers::validateUrl($acct['url']))->values()->toArray();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
'following' => $res
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleSubmit(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_masto_webfinger'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_masto_raid'), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'email' => 'required|email:strict,filter_unicode,dns,spoof',
|
||||||
|
'username' => [
|
||||||
|
'required',
|
||||||
|
'min:2',
|
||||||
|
'max:15',
|
||||||
|
'unique:users,username',
|
||||||
|
function ($attribute, $value, $fail) {
|
||||||
|
$dash = substr_count($value, '-');
|
||||||
|
$underscore = substr_count($value, '_');
|
||||||
|
$period = substr_count($value, '.');
|
||||||
|
|
||||||
|
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||||
|
return $fail('Username is invalid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(($dash + $underscore + $period) > 1) {
|
||||||
|
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctype_alnum($value[0])) {
|
||||||
|
return $fail('Username is invalid. Must start with a letter or number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||||
|
return $fail('Username is invalid. Must end with a letter or number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$val = str_replace(['_', '.', '-'], '', $value);
|
||||||
|
if(!ctype_alnum($val)) {
|
||||||
|
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||||
|
}
|
||||||
|
|
||||||
|
$restricted = RestrictedNames::get();
|
||||||
|
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||||
|
return $fail('Username cannot be used.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'password' => 'required|string|min:8|confirmed',
|
||||||
|
'name' => 'nullable|max:30'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$email = $request->input('email');
|
||||||
|
$username = $request->input('username');
|
||||||
|
$password = $request->input('password');
|
||||||
|
$name = $request->input('name');
|
||||||
|
|
||||||
|
$user = $this->createUser([
|
||||||
|
'name' => $name,
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $password,
|
||||||
|
'email' => $email
|
||||||
|
]);
|
||||||
|
|
||||||
|
$raid = $request->session()->pull('oauth_masto_raid');
|
||||||
|
$webfinger = $request->session()->pull('oauth_masto_webfinger');
|
||||||
|
$token = $user->createToken('Onboarding')->accessToken;
|
||||||
|
|
||||||
|
$ra = RemoteAuth::where('id', $raid)->where('webfinger', $webfinger)->firstOrFail();
|
||||||
|
$ra->user_id = $user->id;
|
||||||
|
$ra->save();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 200,
|
||||||
|
'msg' => 'Success',
|
||||||
|
'token' => $token
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeBio(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_unless($request->user(), 404);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'bio' => 'required|nullable|max:500',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = $request->user()->profile;
|
||||||
|
$profile->bio = Purify::clean($request->input('bio'));
|
||||||
|
$profile->save();
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function accountToId(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_if($request->user(), 404);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'account' => 'required|url'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$account = $request->input('account');
|
||||||
|
abort_unless(substr(strtolower($account), 0, 8) === 'https://', 404);
|
||||||
|
|
||||||
|
$host = strtolower(config('pixelfed.domain.app'));
|
||||||
|
$domain = strtolower(parse_url($account, PHP_URL_HOST));
|
||||||
|
|
||||||
|
if($domain == $host) {
|
||||||
|
$username = Str::of($account)->explode('/')->last();
|
||||||
|
$user = User::where('username', $username)->first();
|
||||||
|
if($user) {
|
||||||
|
return ['id' => (string) $user->profile_id];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$profile = Helpers::profileFetch($account);
|
||||||
|
if($profile) {
|
||||||
|
return ['id' => (string) $profile->id];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} catch (\GuzzleHttp\Exception\RequestException $e) {
|
||||||
|
return;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeAvatar(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_unless($request->user(), 404);
|
||||||
|
$this->validate($request, [
|
||||||
|
'avatar_url' => 'required|active_url',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
$profile = $user->profile;
|
||||||
|
|
||||||
|
abort_if(!$profile->avatar, 404, 'Missing avatar');
|
||||||
|
|
||||||
|
$avatar = $profile->avatar;
|
||||||
|
$avatar->remote_url = $request->input('avatar_url');
|
||||||
|
$avatar->save();
|
||||||
|
|
||||||
|
MediaStorageService::avatar($avatar, config_cache('pixelfed.cloud_storage') == false);
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finishUp(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_unless($request->user(), 404);
|
||||||
|
|
||||||
|
$currentWebfinger = '@' . $request->user()->username . '@' . config('pixelfed.domain.app');
|
||||||
|
$ra = RemoteAuth::where('user_id', $request->user()->id)->firstOrFail();
|
||||||
|
RemoteAuthService::submitToBeagle(
|
||||||
|
$ra->webfinger,
|
||||||
|
$ra->verify_credentials['url'],
|
||||||
|
$currentWebfinger,
|
||||||
|
$request->user()->url()
|
||||||
|
);
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleLogin(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((
|
||||||
|
config_cache('pixelfed.open_registration') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
) || (
|
||||||
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
|
config('remote-auth.mastodon.enabled')
|
||||||
|
), 404);
|
||||||
|
abort_if($request->user(), 404);
|
||||||
|
abort_unless($request->session()->exists('oauth_domain'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
|
||||||
|
abort_unless($request->session()->exists('oauth_masto_webfinger'), 403);
|
||||||
|
|
||||||
|
$domain = $request->session()->get('oauth_domain');
|
||||||
|
$wf = $request->session()->get('oauth_masto_webfinger');
|
||||||
|
|
||||||
|
$ra = RemoteAuth::where('webfinger', $wf)->where('domain', $domain)->whereNotNull('user_id')->firstOrFail();
|
||||||
|
|
||||||
|
$user = User::findOrFail($ra->user_id);
|
||||||
|
abort_if($user->is_admin || $user->status != null, 422, 'Invalid auth action');
|
||||||
|
Auth::loginUsingId($ra->user_id);
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createUser($data)
|
||||||
|
{
|
||||||
|
event(new Registered($user = User::create([
|
||||||
|
'name' => Purify::clean($data['name']),
|
||||||
|
'username' => $data['username'],
|
||||||
|
'email' => $data['email'],
|
||||||
|
'password' => Hash::make($data['password']),
|
||||||
|
'email_verified_at' => config('remote-auth.mastodon.contraints.skip_email_verification') ? now() : null,
|
||||||
|
'app_register_ip' => request()->ip(),
|
||||||
|
'register_source' => 'mastodon'
|
||||||
|
])));
|
||||||
|
|
||||||
|
$this->guarder()->login($user);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function guarder()
|
||||||
|
{
|
||||||
|
return Auth::guard();
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,9 +39,11 @@ trait PrivacySettings
|
||||||
'public_dm',
|
'public_dm',
|
||||||
'show_profile_follower_count',
|
'show_profile_follower_count',
|
||||||
'show_profile_following_count',
|
'show_profile_following_count',
|
||||||
|
'indexable',
|
||||||
'show_atom',
|
'show_atom',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$profile->indexable = $request->input('indexable') == 'on';
|
||||||
$profile->is_suggestable = $request->input('is_suggestable') == 'on';
|
$profile->is_suggestable = $request->input('is_suggestable') == 'on';
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
|
@ -70,6 +72,8 @@ trait PrivacySettings
|
||||||
} else {
|
} else {
|
||||||
$settings->{$field} = false;
|
$settings->{$field} = false;
|
||||||
}
|
}
|
||||||
|
} elseif ($field == 'indexable') {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if ($form == 'on') {
|
if ($form == 'on') {
|
||||||
$settings->{$field} = true;
|
$settings->{$field} = true;
|
||||||
|
|
|
@ -230,29 +230,51 @@ class SettingsController extends Controller
|
||||||
|
|
||||||
public function timelineSettings(Request $request)
|
public function timelineSettings(Request $request)
|
||||||
{
|
{
|
||||||
|
$uid = $request->user()->id;
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$top = Redis::zscore('pf:tl:top', $pid) != false;
|
$top = Redis::zscore('pf:tl:top', $pid) != false;
|
||||||
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
|
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
|
||||||
return view('settings.timeline', compact('top', 'replies'));
|
$userSettings = UserSetting::firstOrCreate([
|
||||||
|
'user_id' => $uid
|
||||||
|
]);
|
||||||
|
if(!$userSettings || !$userSettings->other) {
|
||||||
|
$userSettings = [
|
||||||
|
'enable_reblogs' => false,
|
||||||
|
'photo_reblogs_only' => false
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$userSettings = array_merge([
|
||||||
|
'enable_reblogs' => false,
|
||||||
|
'photo_reblogs_only' => false
|
||||||
|
],
|
||||||
|
$userSettings->other);
|
||||||
|
}
|
||||||
|
return view('settings.timeline', compact('top', 'replies', 'userSettings'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateTimelineSettings(Request $request)
|
public function updateTimelineSettings(Request $request)
|
||||||
{
|
{
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$top = $request->has('top') && $request->input('top') === 'on';
|
$uid = $request->user()->id;
|
||||||
$replies = $request->has('replies') && $request->input('replies') === 'on';
|
$this->validate($request, [
|
||||||
|
'enable_reblogs' => 'sometimes',
|
||||||
if($top) {
|
'photo_reblogs_only' => 'sometimes'
|
||||||
Redis::zadd('pf:tl:top', $pid, $pid);
|
]);
|
||||||
} else {
|
|
||||||
Redis::zrem('pf:tl:top', $pid);
|
Redis::zrem('pf:tl:top', $pid);
|
||||||
}
|
|
||||||
|
|
||||||
if($replies) {
|
|
||||||
Redis::zadd('pf:tl:replies', $pid, $pid);
|
|
||||||
} else {
|
|
||||||
Redis::zrem('pf:tl:replies', $pid);
|
Redis::zrem('pf:tl:replies', $pid);
|
||||||
|
$userSettings = UserSetting::firstOrCreate([
|
||||||
|
'user_id' => $uid
|
||||||
|
]);
|
||||||
|
if($userSettings->other) {
|
||||||
|
$other = $userSettings->other;
|
||||||
|
$other['enable_reblogs'] = $request->has('enable_reblogs');
|
||||||
|
$other['photo_reblogs_only'] = $request->has('photo_reblogs_only');
|
||||||
|
} else {
|
||||||
|
$other['enable_reblogs'] = $request->has('enable_reblogs');
|
||||||
|
$other['photo_reblogs_only'] = $request->has('photo_reblogs_only');
|
||||||
}
|
}
|
||||||
|
$userSettings->other = $other;
|
||||||
|
$userSettings->save();
|
||||||
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
|
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
use App\Jobs\SharePipeline\SharePipeline;
|
use App\Jobs\SharePipeline\SharePipeline;
|
||||||
use App\Jobs\SharePipeline\UndoSharePipeline;
|
use App\Jobs\SharePipeline\UndoSharePipeline;
|
||||||
use App\AccountInterstitial;
|
use App\AccountInterstitial;
|
||||||
|
@ -242,7 +243,7 @@ class StatusController extends Controller
|
||||||
Cache::forget('profile:embed:' . $status->profile_id);
|
Cache::forget('profile:embed:' . $status->profile_id);
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
$status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
|
||||||
}
|
}
|
||||||
} else if ($status->profile_id == $user->profile_id || $user->is_admin == true) {
|
} else if ($status->profile_id == $user->profile_id || $user->is_admin == true) {
|
||||||
Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
|
Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
|
||||||
|
@ -250,7 +251,7 @@ class StatusController extends Controller
|
||||||
Cache::forget('profile:embed:' . $status->profile_id);
|
Cache::forget('profile:embed:' . $status->profile_id);
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
$status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($request->wantsJson()) {
|
if($request->wantsJson()) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ use App\Jobs\StoryPipeline\StoryViewDeliver;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\MediaPathService;
|
use App\Services\MediaPathService;
|
||||||
use App\Services\StoryService;
|
use App\Services\StoryService;
|
||||||
|
use App\Http\Resources\StoryView as StoryViewResource;
|
||||||
|
|
||||||
class StoryApiV1Controller extends Controller
|
class StoryApiV1Controller extends Controller
|
||||||
{
|
{
|
||||||
|
@ -355,4 +356,26 @@ class StoryApiV1Controller extends Controller
|
||||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension());
|
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension());
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function viewers(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'sid' => 'required|string|min:1|max:50'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
|
||||||
|
$story = Story::whereProfileId($pid)
|
||||||
|
->whereActive(true)
|
||||||
|
->findOrFail($sid);
|
||||||
|
|
||||||
|
$viewers = StoryView::whereStoryId($story->id)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->cursorPaginate(10);
|
||||||
|
|
||||||
|
return StoryViewResource::collection($viewers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
30
app/Http/Resources/AdminProfile.php
Normal file
30
app/Http/Resources/AdminProfile.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
|
||||||
|
class AdminProfile extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
$res = AccountService::get($this->id, true);
|
||||||
|
$res['domain'] = $this->domain;
|
||||||
|
$res['status'] = $this->status;
|
||||||
|
$res['limits'] = [
|
||||||
|
'exist' => $this->cw || $this->unlisted || $this->no_autolink,
|
||||||
|
'autocw' => (bool) $this->cw,
|
||||||
|
'unlisted' => (bool) $this->unlisted,
|
||||||
|
'no_autolink' => (bool) $this->no_autolink,
|
||||||
|
'banned' => (bool) $this->status == 'banned'
|
||||||
|
];
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
20
app/Http/Resources/StoryView.php
Normal file
20
app/Http/Resources/StoryView.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
|
||||||
|
class StoryView extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request)
|
||||||
|
{
|
||||||
|
return AccountService::get($this->profile_id, true);
|
||||||
|
}
|
||||||
|
}
|
139
app/Jobs/AdminPipeline/AdminProfileActionPipeline.php
Normal file
139
app/Jobs/AdminPipeline/AdminProfileActionPipeline.php
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\AdminPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Avatar;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Instance;
|
||||||
|
use App\Media;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Status;
|
||||||
|
use Cache;
|
||||||
|
use Storage;
|
||||||
|
use Purify;
|
||||||
|
use App\Services\ActivityPubFetchService;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\MediaStorageService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
|
|
||||||
|
class AdminProfileActionPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $action;
|
||||||
|
protected $profile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct($profile, $action)
|
||||||
|
{
|
||||||
|
$this->profile = $profile;
|
||||||
|
$this->action = $action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$profile = $this->profile;
|
||||||
|
$action = $this->action;
|
||||||
|
|
||||||
|
switch($action) {
|
||||||
|
case 'mark-all-cw':
|
||||||
|
return $this->markAllPostsWithContentWarnings();
|
||||||
|
break;
|
||||||
|
case 'unlist-all':
|
||||||
|
return $this->unlistAllPosts();
|
||||||
|
break;
|
||||||
|
case 'purge':
|
||||||
|
return $this->purgeAllPosts();
|
||||||
|
break;
|
||||||
|
case 'refetch':
|
||||||
|
return $this->refetchAllPosts();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function markAllPostsWithContentWarnings()
|
||||||
|
{
|
||||||
|
$profile = $this->profile;
|
||||||
|
|
||||||
|
foreach(Status::whereProfileId($profile->id)->lazyById(10, 'id') as $status) {
|
||||||
|
if($status->scope == 'direct') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$status->is_nsfw = true;
|
||||||
|
$status->save();
|
||||||
|
StatusService::del($status->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unlistAllPosts()
|
||||||
|
{
|
||||||
|
$profile = $this->profile;
|
||||||
|
|
||||||
|
foreach(Status::whereProfileId($profile->id)->lazyById(10, 'id') as $status) {
|
||||||
|
if($status->scope != 'public') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$status->scope = 'unlisted';
|
||||||
|
$status->visibility = 'unlisted';
|
||||||
|
$status->save();
|
||||||
|
StatusService::del($status->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function purgeAllPosts()
|
||||||
|
{
|
||||||
|
$profile = $this->profile;
|
||||||
|
|
||||||
|
foreach(Status::withTrashed()->whereProfileId($profile->id)->lazyById(10, 'id') as $status) {
|
||||||
|
RemoteStatusDelete::dispatch($status)->onQueue('delete');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function refetchAllPosts()
|
||||||
|
{
|
||||||
|
$profile = $this->profile;
|
||||||
|
$res = ActivityPubFetchService::get($profile->remote_url, false);
|
||||||
|
if(!$res) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
$profile->following_count = Follower::whereProfileId($profile->id)->count();
|
||||||
|
$profile->followers_count = Follower::whereFollowingId($profile->id)->count();
|
||||||
|
$profile->name = isset($res['name']) ? Purify::clean($res['name']) : $profile->username;
|
||||||
|
$profile->bio = isset($res['summary']) ? Purify::clean($res['summary']) : null;
|
||||||
|
if(isset($res['publicKey'])) {
|
||||||
|
$profile->public_key = $res['publicKey']['publicKeyPem'];
|
||||||
|
}
|
||||||
|
if(
|
||||||
|
isset($res['icon']) &&
|
||||||
|
isset(
|
||||||
|
$res['icon']['type'],
|
||||||
|
$res['icon']['mediaType'],
|
||||||
|
$res['icon']['url']) && $res['icon']['type'] == 'Image'
|
||||||
|
) {
|
||||||
|
if(in_array($res['icon']['mediaType'], ['image/jpeg', 'image/png'])) {
|
||||||
|
$profile->avatar->remote_url = $res['icon']['url'];
|
||||||
|
$profile->push();
|
||||||
|
MediaStorageService::avatar($profile->avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$profile->save();
|
||||||
|
AccountService::del($profile->id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,7 @@ use App\Models\Conversation;
|
||||||
use App\Models\Poll;
|
use App\Models\Poll;
|
||||||
use App\Models\PollVote;
|
use App\Models\PollVote;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
|
|
||||||
class DeleteRemoteProfilePipeline implements ShouldQueue
|
class DeleteRemoteProfilePipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -86,7 +87,7 @@ class DeleteRemoteProfilePipeline implements ShouldQueue
|
||||||
Status::whereProfileId($pid)
|
Status::whereProfileId($pid)
|
||||||
->chunk(50, function($statuses) {
|
->chunk(50, function($statuses) {
|
||||||
foreach($statuses as $status) {
|
foreach($statuses as $status) {
|
||||||
DeleteRemoteStatusPipeline::dispatch($status)->onQueue('delete');
|
RemoteStatusDelete::dispatch($status)->onQueue('delete');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -53,10 +53,10 @@ class FanoutDeletePipeline implements ShouldQueue
|
||||||
"id" => $profile->permalink('#delete'),
|
"id" => $profile->permalink('#delete'),
|
||||||
"type" => "Delete",
|
"type" => "Delete",
|
||||||
"actor" => $profile->permalink(),
|
"actor" => $profile->permalink(),
|
||||||
"to" => [
|
"object" => [
|
||||||
"https://www.w3.org/ns/activitystreams#Public",
|
"type" => "Person",
|
||||||
|
"id" => $profile->permalink()
|
||||||
],
|
],
|
||||||
"object" => $profile->permalink(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$payload = json_encode($activity);
|
$payload = json_encode($activity);
|
||||||
|
|
|
@ -53,44 +53,24 @@ class FollowPipeline implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($target->domain || !$target->private_key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Cache::forget('profile:following:' . $actor->id);
|
Cache::forget('profile:following:' . $actor->id);
|
||||||
Cache::forget('profile:following:' . $target->id);
|
Cache::forget('profile:following:' . $target->id);
|
||||||
|
|
||||||
FollowerService::add($actor->id, $target->id);
|
FollowerService::add($actor->id, $target->id);
|
||||||
|
|
||||||
$actorProfileSync = Cache::get(FollowerService::FOLLOWING_SYNC_KEY . $actor->id);
|
|
||||||
if(!$actorProfileSync) {
|
|
||||||
FollowServiceWarmCache::dispatch($actor->id)->onQueue('low');
|
|
||||||
} else {
|
|
||||||
if($actor->following_count) {
|
|
||||||
$actor->increment('following_count');
|
|
||||||
} else {
|
|
||||||
$count = Follower::whereProfileId($actor->id)->count();
|
$count = Follower::whereProfileId($actor->id)->count();
|
||||||
$actor->following_count = $count;
|
$actor->following_count = $count;
|
||||||
$actor->save();
|
$actor->save();
|
||||||
}
|
|
||||||
Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $actor->id, 1, 604800);
|
|
||||||
AccountService::del($actor->id);
|
AccountService::del($actor->id);
|
||||||
}
|
|
||||||
|
|
||||||
$targetProfileSync = Cache::get(FollowerService::FOLLOWERS_SYNC_KEY . $target->id);
|
|
||||||
if(!$targetProfileSync) {
|
|
||||||
FollowServiceWarmCache::dispatch($target->id)->onQueue('low');
|
|
||||||
} else {
|
|
||||||
if($target->followers_count) {
|
|
||||||
$target->increment('followers_count');
|
|
||||||
} else {
|
|
||||||
$count = Follower::whereFollowingId($target->id)->count();
|
$count = Follower::whereFollowingId($target->id)->count();
|
||||||
$target->followers_count = $count;
|
$target->followers_count = $count;
|
||||||
$target->save();
|
$target->save();
|
||||||
}
|
|
||||||
Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $target->id, 1, 604800);
|
|
||||||
AccountService::del($target->id);
|
AccountService::del($target->id);
|
||||||
}
|
|
||||||
|
|
||||||
if($target->domain || !$target->private_key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$notification = new Notification();
|
$notification = new Notification();
|
||||||
|
|
|
@ -8,10 +8,13 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use Cache;
|
use Cache;
|
||||||
use DB;
|
use DB;
|
||||||
|
use Storage;
|
||||||
|
use App\Follower;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
|
||||||
class FollowServiceWarmCache implements ShouldQueue
|
class FollowServiceWarmCache implements ShouldQueue
|
||||||
|
@ -23,6 +26,16 @@ class FollowServiceWarmCache implements ShouldQueue
|
||||||
public $timeout = 5000;
|
public $timeout = 5000;
|
||||||
public $failOnTimeout = false;
|
public $failOnTimeout = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->profileId))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
|
@ -42,6 +55,10 @@ class FollowServiceWarmCache implements ShouldQueue
|
||||||
{
|
{
|
||||||
$id = $this->profileId;
|
$id = $this->profileId;
|
||||||
|
|
||||||
|
if(Cache::has(FollowerService::FOLLOWERS_SYNC_KEY . $id) && Cache::has(FollowerService::FOLLOWING_SYNC_KEY . $id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$account = AccountService::get($id, true);
|
$account = AccountService::get($id, true);
|
||||||
|
|
||||||
if(!$account) {
|
if(!$account) {
|
||||||
|
@ -50,25 +67,43 @@ class FollowServiceWarmCache implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::table('followers')
|
$hasFollowerPostProcessing = false;
|
||||||
->select('id', 'following_id', 'profile_id')
|
$hasFollowingPostProcessing = false;
|
||||||
->whereFollowingId($id)
|
|
||||||
->orderBy('id')
|
|
||||||
->chunk(200, function($followers) use($id) {
|
|
||||||
foreach($followers as $follow) {
|
|
||||||
FollowerService::add($follow->profile_id, $id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
DB::table('followers')
|
if(Follower::whereProfileId($id)->orWhere('following_id', $id)->count()) {
|
||||||
->select('id', 'following_id', 'profile_id')
|
$following = [];
|
||||||
->whereProfileId($id)
|
$followers = [];
|
||||||
->orderBy('id')
|
foreach(Follower::lazy() as $follow) {
|
||||||
->chunk(200, function($followers) use($id) {
|
if($follow->following_id != $id && $follow->profile_id != $id) {
|
||||||
foreach($followers as $follow) {
|
continue;
|
||||||
FollowerService::add($id, $follow->following_id);
|
}
|
||||||
|
if($follow->profile_id == $id) {
|
||||||
|
$following[] = $follow->following_id;
|
||||||
|
} else {
|
||||||
|
$followers[] = $follow->profile_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(count($followers) > 100) {
|
||||||
|
// store follower ids and process in another job
|
||||||
|
Storage::put('follow-warm-cache/' . $id . '/followers.json', json_encode($followers));
|
||||||
|
$hasFollowerPostProcessing = true;
|
||||||
|
} else {
|
||||||
|
foreach($followers as $follower) {
|
||||||
|
FollowerService::add($follower, $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(count($following) > 100) {
|
||||||
|
// store following ids and process in another job
|
||||||
|
Storage::put('follow-warm-cache/' . $id . '/following.json', json_encode($following));
|
||||||
|
$hasFollowingPostProcessing = true;
|
||||||
|
} else {
|
||||||
|
foreach($following as $following) {
|
||||||
|
FollowerService::add($id, $following);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $id, 1, 604800);
|
Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $id, 1, 604800);
|
||||||
Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $id, 1, 604800);
|
Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $id, 1, 604800);
|
||||||
|
@ -82,6 +117,14 @@ class FollowServiceWarmCache implements ShouldQueue
|
||||||
|
|
||||||
AccountService::del($id);
|
AccountService::del($id);
|
||||||
|
|
||||||
|
if($hasFollowingPostProcessing) {
|
||||||
|
FollowServiceWarmCacheLargeIngestPipeline::dispatch($id, 'following')->onQueue('follow');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($hasFollowerPostProcessing) {
|
||||||
|
FollowServiceWarmCacheLargeIngestPipeline::dispatch($id, 'followers')->onQueue('follow');
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\FollowPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\FollowerService;
|
||||||
|
use Cache;
|
||||||
|
use DB;
|
||||||
|
use Storage;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
|
||||||
|
class FollowServiceWarmCacheLargeIngestPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $profileId;
|
||||||
|
public $followType;
|
||||||
|
public $tries = 5;
|
||||||
|
public $timeout = 5000;
|
||||||
|
public $failOnTimeout = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct($profileId, $followType = 'following')
|
||||||
|
{
|
||||||
|
$this->profileId = $profileId;
|
||||||
|
$this->followType = $followType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$pid = $this->profileId;
|
||||||
|
$type = $this->followType;
|
||||||
|
|
||||||
|
if($type === 'followers') {
|
||||||
|
$key = 'follow-warm-cache/' . $pid . '/followers.json';
|
||||||
|
if(!Storage::exists($key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$file = Storage::get($key);
|
||||||
|
$json = json_decode($file, true);
|
||||||
|
|
||||||
|
foreach($json as $id) {
|
||||||
|
FollowerService::add($id, $pid, false);
|
||||||
|
usleep(random_int(500, 3000));
|
||||||
|
}
|
||||||
|
sleep(5);
|
||||||
|
Storage::delete($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($type === 'following') {
|
||||||
|
$key = 'follow-warm-cache/' . $pid . '/following.json';
|
||||||
|
if(!Storage::exists($key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$file = Storage::get($key);
|
||||||
|
$json = json_decode($file, true);
|
||||||
|
|
||||||
|
foreach($json as $id) {
|
||||||
|
FollowerService::add($pid, $id, false);
|
||||||
|
usleep(random_int(500, 3000));
|
||||||
|
}
|
||||||
|
sleep(5);
|
||||||
|
Storage::delete($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(random_int(2, 5));
|
||||||
|
$files = Storage::files('follow-warm-cache/' . $pid);
|
||||||
|
if(empty($files)) {
|
||||||
|
Storage::deleteDirectory('follow-warm-cache/' . $pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,10 @@ class SharePipeline implements ShouldQueue
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
$parent = $this->status->parent();
|
$parent = Status::find($this->status->reblog_of_id);
|
||||||
|
if(!$parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$actor = $status->profile;
|
$actor = $status->profile;
|
||||||
$target = $parent->profile;
|
$target = $parent->profile;
|
||||||
|
|
||||||
|
@ -84,7 +87,7 @@ class SharePipeline implements ShouldQueue
|
||||||
|
|
||||||
public function remoteAnnounceDeliver()
|
public function remoteAnnounceDeliver()
|
||||||
{
|
{
|
||||||
if(config_cache('federation.activitypub.enabled') == false) {
|
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
|
|
|
@ -61,7 +61,7 @@ class UndoSharePipeline implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config_cache('federation.activitypub.enabled') == false) {
|
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
||||||
return $status->delete();
|
return $status->delete();
|
||||||
} else {
|
} else {
|
||||||
return $this->remoteAnnounceDeliver();
|
return $this->remoteAnnounceDeliver();
|
||||||
|
@ -70,7 +70,8 @@ class UndoSharePipeline implements ShouldQueue
|
||||||
|
|
||||||
public function remoteAnnounceDeliver()
|
public function remoteAnnounceDeliver()
|
||||||
{
|
{
|
||||||
if(config_cache('federation.activitypub.enabled') == false) {
|
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
||||||
|
$status->delete();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
142
app/Jobs/StatusPipeline/RemoteStatusDelete.php
Normal file
142
app/Jobs/StatusPipeline/RemoteStatusDelete.php
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\StatusPipeline;
|
||||||
|
|
||||||
|
use DB, Cache, Storage;
|
||||||
|
use App\{
|
||||||
|
AccountInterstitial,
|
||||||
|
Bookmark,
|
||||||
|
CollectionItem,
|
||||||
|
DirectMessage,
|
||||||
|
Like,
|
||||||
|
Media,
|
||||||
|
MediaTag,
|
||||||
|
Mention,
|
||||||
|
Notification,
|
||||||
|
Report,
|
||||||
|
Status,
|
||||||
|
StatusArchived,
|
||||||
|
StatusHashtag,
|
||||||
|
StatusView
|
||||||
|
};
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use League\Fractal;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
use App\Transformer\ActivityPub\Verb\DeleteNote;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use GuzzleHttp\Pool;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Promise;
|
||||||
|
use App\Util\ActivityPub\HttpSignature;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\CollectionService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||||
|
|
||||||
|
class RemoteStatusDelete implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
public $timeout = 90;
|
||||||
|
public $tries = 2;
|
||||||
|
public $maxExceptions = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Status $status)
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$status = $this->status;
|
||||||
|
|
||||||
|
if($status->deleted_at) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$profile = $this->status->profile;
|
||||||
|
|
||||||
|
StatusService::del($status->id, true);
|
||||||
|
|
||||||
|
if($profile->status_count && $profile->status_count > 0) {
|
||||||
|
$profile->status_count = $profile->status_count - 1;
|
||||||
|
$profile->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->unlinkRemoveMedia($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unlinkRemoveMedia($status)
|
||||||
|
{
|
||||||
|
|
||||||
|
if($status->in_reply_to_id) {
|
||||||
|
$parent = Status::find($status->in_reply_to_id);
|
||||||
|
if($parent) {
|
||||||
|
--$parent->reply_count;
|
||||||
|
$parent->save();
|
||||||
|
StatusService::del($parent->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountInterstitial::where('item_type', 'App\Status')
|
||||||
|
->where('item_id', $status->id)
|
||||||
|
->delete();
|
||||||
|
Bookmark::whereStatusId($status->id)->delete();
|
||||||
|
CollectionItem::whereObjectType('App\Status')
|
||||||
|
->whereObjectId($status->id)
|
||||||
|
->get()
|
||||||
|
->each(function($col) {
|
||||||
|
CollectionService::removeItem($col->collection_id, $col->object_id);
|
||||||
|
$col->delete();
|
||||||
|
});
|
||||||
|
DirectMessage::whereStatusId($status->id)->delete();
|
||||||
|
Like::whereStatusId($status->id)->forceDelete();
|
||||||
|
Media::whereStatusId($status->id)
|
||||||
|
->get()
|
||||||
|
->each(function($media) {
|
||||||
|
MediaDeletePipeline::dispatch($media)->onQueue('mmo');
|
||||||
|
});
|
||||||
|
MediaTag::where('status_id', $status->id)->delete();
|
||||||
|
Mention::whereStatusId($status->id)->forceDelete();
|
||||||
|
Notification::whereItemType('App\Status')
|
||||||
|
->whereItemId($status->id)
|
||||||
|
->forceDelete();
|
||||||
|
Report::whereObjectType('App\Status')
|
||||||
|
->whereObjectId($status->id)
|
||||||
|
->delete();
|
||||||
|
StatusArchived::whereStatusId($status->id)->delete();
|
||||||
|
StatusHashtag::whereStatusId($status->id)->delete();
|
||||||
|
StatusView::whereStatusId($status->id)->delete();
|
||||||
|
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
||||||
|
|
||||||
|
$status->delete();
|
||||||
|
|
||||||
|
StatusService::del($status->id, true);
|
||||||
|
AccountService::del($status->profile_id);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use App\Services\UserFilterService;
|
use App\Services\UserFilterService;
|
||||||
|
use App\Services\AdminShadowFilterService;
|
||||||
|
|
||||||
class StatusEntityLexer implements ShouldQueue
|
class StatusEntityLexer implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -176,7 +177,9 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
$status->reblog_of_id === null &&
|
$status->reblog_of_id === null &&
|
||||||
($hideNsfw ? $status->is_nsfw == false : true)
|
($hideNsfw ? $status->is_nsfw == false : true)
|
||||||
) {
|
) {
|
||||||
|
if(AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) {
|
||||||
PublicTimelineService::add($status->id);
|
PublicTimelineService::add($status->id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') {
|
if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') {
|
||||||
|
|
|
@ -45,6 +45,11 @@ class StatusTagsPipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
$res = $this->activity;
|
$res = $this->activity;
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
|
|
||||||
|
if(isset($res['tag']['type'], $res['tag']['name'])) {
|
||||||
|
$res['tag'] = [$res['tag']];
|
||||||
|
}
|
||||||
|
|
||||||
$tags = collect($res['tag']);
|
$tags = collect($res['tag']);
|
||||||
|
|
||||||
// Emoji
|
// Emoji
|
||||||
|
@ -73,19 +78,18 @@ class StatusTagsPipeline implements ShouldQueue
|
||||||
|
|
||||||
if(config('database.default') === 'pgsql') {
|
if(config('database.default') === 'pgsql') {
|
||||||
$hashtag = Hashtag::where('name', 'ilike', $name)
|
$hashtag = Hashtag::where('name', 'ilike', $name)
|
||||||
->orWhere('slug', 'ilike', str_slug($name))
|
->orWhere('slug', 'ilike', str_slug($name, '-', false))
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if(!$hashtag) {
|
if(!$hashtag) {
|
||||||
$hashtag = new Hashtag;
|
$hashtag = Hashtag::updateOrCreate([
|
||||||
$hashtag->name = $name;
|
'slug' => str_slug($name, '-', false),
|
||||||
$hashtag->slug = str_slug($name);
|
'name' => $name
|
||||||
$hashtag->save();
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$hashtag = Hashtag::firstOrCreate([
|
$hashtag = Hashtag::updateOrCreate([
|
||||||
'slug' => str_slug($name)
|
'slug' => str_slug($name, '-', false),
|
||||||
], [
|
|
||||||
'name' => $name
|
'name' => $name
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,9 @@ class Media extends Model
|
||||||
|
|
||||||
public function mimeType()
|
public function mimeType()
|
||||||
{
|
{
|
||||||
|
if(!$this->mime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return explode('/', $this->mime)[0];
|
return explode('/', $this->mime)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
app/Models/AdminShadowFilter.php
Normal file
27
app/Models/AdminShadowFilter.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
|
||||||
|
class AdminShadowFilter extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'created_at' => 'datetime'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function account()
|
||||||
|
{
|
||||||
|
if($this->item_type === 'App\Profile') {
|
||||||
|
return AccountService::get($this->item_id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
17
app/Models/ProfileAlias.php
Normal file
17
app/Models/ProfileAlias.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Profile;
|
||||||
|
|
||||||
|
class ProfileAlias extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
public function profile()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class);
|
||||||
|
}
|
||||||
|
}
|
19
app/Models/RemoteAuth.php
Normal file
19
app/Models/RemoteAuth.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class RemoteAuth extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'verify_credentials' => 'array',
|
||||||
|
'last_successful_login_at' => 'datetime',
|
||||||
|
'last_verify_credentials_at' => 'datetime'
|
||||||
|
];
|
||||||
|
}
|
13
app/Models/RemoteAuthInstance.php
Normal file
13
app/Models/RemoteAuthInstance.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class RemoteAuthInstance extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ use App\Util\Lexer\PrettyNumber;
|
||||||
use App\HasSnowflakePrimary;
|
use App\HasSnowflakePrimary;
|
||||||
use Illuminate\Database\Eloquent\{Model, SoftDeletes};
|
use Illuminate\Database\Eloquent\{Model, SoftDeletes};
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
|
use App\Models\ProfileAlias;
|
||||||
|
|
||||||
class Profile extends Model
|
class Profile extends Model
|
||||||
{
|
{
|
||||||
|
@ -369,9 +370,13 @@ class Profile extends Model
|
||||||
return $this->hasMany(Story::class);
|
return $this->hasMany(Story::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function reported()
|
public function reported()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Report::class, 'object_id');
|
return $this->hasMany(Report::class, 'object_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function aliases()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProfileAlias::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
191
app/Services/Account/RemoteAuthService.php
Normal file
191
app/Services/Account/RemoteAuthService.php
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Account;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use App\Models\RemoteAuthInstance;
|
||||||
|
use Illuminate\Http\Client\ConnectionException;
|
||||||
|
use Illuminate\Http\Client\RequestException;
|
||||||
|
|
||||||
|
class RemoteAuthService
|
||||||
|
{
|
||||||
|
const CACHE_KEY = 'pf:services:remoteauth:';
|
||||||
|
|
||||||
|
public static function getConfig()
|
||||||
|
{
|
||||||
|
return json_encode([
|
||||||
|
'default_only' => config('remote-auth.mastodon.domains.only_default'),
|
||||||
|
'custom_only' => config('remote-auth.mastodon.domains.only_custom'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMastodonClient($domain)
|
||||||
|
{
|
||||||
|
if(RemoteAuthInstance::whereDomain($domain)->exists()) {
|
||||||
|
return RemoteAuthInstance::whereDomain($domain)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$url = 'https://' . $domain . '/api/v1/apps';
|
||||||
|
$res = Http::asForm()->throw()->timeout(10)->post($url, [
|
||||||
|
'client_name' => config('pixelfed.domain.app', 'pixelfed'),
|
||||||
|
'redirect_uris' => url('/auth/mastodon/callback'),
|
||||||
|
'scopes' => 'read',
|
||||||
|
'website' => 'https://pixelfed.org'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(!$res->ok()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = $res->json();
|
||||||
|
|
||||||
|
if(!$body || !isset($body['client_id'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = RemoteAuthInstance::updateOrCreate([
|
||||||
|
'domain' => $domain
|
||||||
|
], [
|
||||||
|
'client_id' => $body['client_id'],
|
||||||
|
'client_secret' => $body['client_secret'],
|
||||||
|
'redirect_uri' => $body['redirect_uri'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getToken($domain, $code)
|
||||||
|
{
|
||||||
|
$raw = RemoteAuthInstance::whereDomain($domain)->first();
|
||||||
|
if(!$raw || !$raw->active || $raw->banned) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = 'https://' . $domain . '/oauth/token';
|
||||||
|
$res = Http::asForm()->post($url, [
|
||||||
|
'code' => $code,
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => $raw->client_id,
|
||||||
|
'client_secret' => $raw->client_secret,
|
||||||
|
'redirect_uri' => $raw->redirect_uri,
|
||||||
|
'scope' => 'read'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getVerifyCredentials($domain, $code)
|
||||||
|
{
|
||||||
|
$raw = RemoteAuthInstance::whereDomain($domain)->first();
|
||||||
|
if(!$raw || !$raw->active || $raw->banned) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = 'https://' . $domain . '/api/v1/accounts/verify_credentials';
|
||||||
|
|
||||||
|
$res = Http::withToken($code)->get($url);
|
||||||
|
|
||||||
|
return $res->json();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getFollowing($domain, $code, $id)
|
||||||
|
{
|
||||||
|
$raw = RemoteAuthInstance::whereDomain($domain)->first();
|
||||||
|
if(!$raw || !$raw->active || $raw->banned) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = 'https://' . $domain . '/api/v1/accounts/' . $id . '/following?limit=80';
|
||||||
|
$key = self::CACHE_KEY . 'get-following:code:' . substr($code, 0, 16) . substr($code, -5) . ':domain:' . $domain. ':id:' .$id;
|
||||||
|
|
||||||
|
return Cache::remember($key, 3600, function() use($url, $code) {
|
||||||
|
$res = Http::withToken($code)->get($url);
|
||||||
|
return $res->json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isDomainCompatible($domain = false)
|
||||||
|
{
|
||||||
|
if(!$domain) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache::remember(self::CACHE_KEY . 'domain-compatible:' . $domain, 14400, function() use($domain) {
|
||||||
|
try {
|
||||||
|
$res = Http::timeout(20)->retry(3, 750)->get('https://beagle.pixelfed.net/api/v1/raa/domain?domain=' . $domain);
|
||||||
|
if(!$res->ok()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$json = $res->json();
|
||||||
|
|
||||||
|
if(!in_array('compatible', $json)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res['compatible'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function lookupWebfingerUses($wf)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$res = Http::timeout(20)->retry(3, 750)->get('https://beagle.pixelfed.net/api/v1/raa/lookup?webfinger=' . $wf);
|
||||||
|
if(!$res->ok()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
return false;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$json = $res->json();
|
||||||
|
if(!$json || !isset($json['count'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $json['count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function submitToBeagle($ow, $ou, $dw, $du)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$url = 'https://beagle.pixelfed.net/api/v1/raa/submit';
|
||||||
|
$res = Http::throw()->timeout(10)->get($url, [
|
||||||
|
'ow' => $ow,
|
||||||
|
'ou' => $ou,
|
||||||
|
'dw' => $dw,
|
||||||
|
'du' => $du,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(!$res->ok()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
return;
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
return;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,11 +11,13 @@ use Illuminate\Http\Client\RequestException;
|
||||||
|
|
||||||
class ActivityPubFetchService
|
class ActivityPubFetchService
|
||||||
{
|
{
|
||||||
public static function get($url)
|
public static function get($url, $validateUrl = true)
|
||||||
{
|
{
|
||||||
|
if($validateUrl === true) {
|
||||||
if(!Helpers::validateUrl($url)) {
|
if(!Helpers::validateUrl($url)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$baseHeaders = [
|
$baseHeaders = [
|
||||||
'Accept' => 'application/activity+json, application/ld+json',
|
'Accept' => 'application/activity+json, application/ld+json',
|
||||||
|
|
51
app/Services/AdminShadowFilterService.php
Normal file
51
app/Services/AdminShadowFilterService.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\AdminShadowFilter;
|
||||||
|
use Cache;
|
||||||
|
|
||||||
|
class AdminShadowFilterService
|
||||||
|
{
|
||||||
|
const CACHE_KEY = 'pf:services:asfs:';
|
||||||
|
|
||||||
|
public static function queryFilter($name = 'hide_from_public_feeds')
|
||||||
|
{
|
||||||
|
return AdminShadowFilter::whereItemType('App\Profile')
|
||||||
|
->whereActive(1)
|
||||||
|
->where('hide_from_public_feeds', true)
|
||||||
|
->pluck('item_id')
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getHideFromPublicFeedsList($refresh = false)
|
||||||
|
{
|
||||||
|
$key = self::CACHE_KEY . 'list:hide_from_public_feeds';
|
||||||
|
if($refresh) {
|
||||||
|
Cache::forget($key);
|
||||||
|
}
|
||||||
|
return Cache::remember($key, 86400, function() {
|
||||||
|
return AdminShadowFilter::whereItemType('App\Profile')
|
||||||
|
->whereActive(1)
|
||||||
|
->where('hide_from_public_feeds', true)
|
||||||
|
->pluck('item_id')
|
||||||
|
->toArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canAddToPublicFeedByProfileId($profileId)
|
||||||
|
{
|
||||||
|
return !in_array($profileId, self::getHideFromPublicFeedsList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function refresh()
|
||||||
|
{
|
||||||
|
$keys = [
|
||||||
|
self::CACHE_KEY . 'list:hide_from_public_feeds'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach($keys as $key) {
|
||||||
|
Cache::forget($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
app/Services/DomainService.php
Normal file
28
app/Services/DomainService.php
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
class DomainService
|
||||||
|
{
|
||||||
|
const CACHE_KEY = 'pf:services:domains:';
|
||||||
|
|
||||||
|
public static function hasValidDns($domain)
|
||||||
|
{
|
||||||
|
if(!$domain || !strlen($domain) || strpos($domain, '.') == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config('security.url.trusted_domains')) {
|
||||||
|
if(in_array($domain, explode(',', config('security.url.trusted_domains')))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache::remember(self::CACHE_KEY . 'valid-dns:' . $domain, 14400, function() use($domain) {
|
||||||
|
return count(dns_get_record($domain, DNS_A | DNS_AAAA)) > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,10 +20,14 @@ class FollowerService
|
||||||
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
|
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
|
||||||
const FOLLOWERS_KEY = 'pf:services:follow:followers:id:';
|
const FOLLOWERS_KEY = 'pf:services:follow:followers:id:';
|
||||||
|
|
||||||
public static function add($actor, $target)
|
public static function add($actor, $target, $refresh = true)
|
||||||
{
|
{
|
||||||
$ts = (int) microtime(true);
|
$ts = (int) microtime(true);
|
||||||
|
if($refresh) {
|
||||||
RelationshipService::refresh($actor, $target);
|
RelationshipService::refresh($actor, $target);
|
||||||
|
} else {
|
||||||
|
RelationshipService::forget($actor, $target);
|
||||||
|
}
|
||||||
Redis::zadd(self::FOLLOWING_KEY . $actor, $ts, $target);
|
Redis::zadd(self::FOLLOWING_KEY . $actor, $ts, $target);
|
||||||
Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor);
|
Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor);
|
||||||
Cache::forget('profile:following:' . $actor);
|
Cache::forget('profile:following:' . $actor);
|
||||||
|
|
|
@ -120,6 +120,9 @@ class InstanceService
|
||||||
$pixels[] = $row;
|
$pixels[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Free the allocated GdImage object from memory:
|
||||||
|
imagedestroy($image);
|
||||||
|
|
||||||
$components_x = 4;
|
$components_x = 4;
|
||||||
$components_y = 4;
|
$components_y = 4;
|
||||||
$blurhash = Blurhash::encode($pixels, $components_x, $components_y);
|
$blurhash = Blurhash::encode($pixels, $components_x, $components_y);
|
||||||
|
|
|
@ -24,10 +24,8 @@ class LikeService {
|
||||||
public static function setAdd($profileId, $statusId)
|
public static function setAdd($profileId, $statusId)
|
||||||
{
|
{
|
||||||
if(self::setCount($profileId) > 400) {
|
if(self::setCount($profileId) > 400) {
|
||||||
if(config('database.redis.client') === 'phpredis') {
|
|
||||||
Redis::zpopmin(self::CACHE_SET_KEY . $profileId);
|
Redis::zpopmin(self::CACHE_SET_KEY . $profileId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Redis::zadd(self::CACHE_SET_KEY . $profileId, $statusId, $statusId);
|
return Redis::zadd(self::CACHE_SET_KEY . $profileId, $statusId, $statusId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use App\Services\AccountService;
|
||||||
use App\Http\Controllers\AvatarController;
|
use App\Http\Controllers\AvatarController;
|
||||||
use GuzzleHttp\Exception\RequestException;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class MediaStorageService {
|
class MediaStorageService {
|
||||||
|
|
||||||
|
@ -42,27 +43,16 @@ class MediaStorageService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$h = $r->getHeaders();
|
$h = Arr::mapWithKeys($r->getHeaders(), function($item, $key) {
|
||||||
|
return [strtolower($key) => last($item)];
|
||||||
|
});
|
||||||
|
|
||||||
if (isset($h['content-length']) && isset($h['content-type'])) {
|
if(!isset($h['content-length'], $h['content-type'])) {
|
||||||
if(empty($h['content-length']) || empty($h['content-type'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$len = is_array($h['content-length']) ? $h['content-length'][0] : $h['content-length'];
|
|
||||||
$mime = is_array($h['content-type']) ? $h['content-type'][0] : $h['content-type'];
|
|
||||||
} else {
|
|
||||||
if (isset($h['Content-Length'], $h['Content-Type']) == false) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(empty($h['Content-Length']) || empty($h['Content-Type']) ) {
|
$len = (int) $h['content-length'];
|
||||||
return false;
|
$mime = $h['content-type'];
|
||||||
}
|
|
||||||
|
|
||||||
$len = is_array($h['Content-Length']) ? $h['Content-Length'][0] : $h['Content-Length'];
|
|
||||||
$mime = is_array($h['Content-Type']) ? $h['Content-Type'][0] : $h['Content-Type'];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if($len < 10 || $len > ((config_cache('pixelfed.max_photo_size') * 1000))) {
|
if($len < 10 || $len > ((config_cache('pixelfed.max_photo_size') * 1000))) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -96,12 +86,11 @@ class MediaStorageService {
|
||||||
$thumbname = array_pop($pt);
|
$thumbname = array_pop($pt);
|
||||||
$storagePath = implode('/', $p);
|
$storagePath = implode('/', $p);
|
||||||
|
|
||||||
$disk = Storage::disk(config('filesystems.cloud'));
|
$url = ResilientMediaStorageService::store($storagePath, $path, $name);
|
||||||
$file = $disk->putFileAs($storagePath, new File($path), $name, 'public');
|
if($thumb) {
|
||||||
$url = $disk->url($file);
|
$thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname);
|
||||||
$thumbFile = $disk->putFileAs($storagePath, new File($thumb), $thumbname, 'public');
|
|
||||||
$thumbUrl = $disk->url($thumbFile);
|
|
||||||
$media->thumbnail_url = $thumbUrl;
|
$media->thumbnail_url = $thumbUrl;
|
||||||
|
}
|
||||||
$media->cdn_url = $url;
|
$media->cdn_url = $url;
|
||||||
$media->optimized_url = $url;
|
$media->optimized_url = $url;
|
||||||
$media->replicated_at = now();
|
$media->replicated_at = now();
|
||||||
|
|
|
@ -49,10 +49,8 @@ class NetworkTimelineService
|
||||||
public static function add($val)
|
public static function add($val)
|
||||||
{
|
{
|
||||||
if(self::count() > config('instance.timeline.network.cache_dropoff')) {
|
if(self::count() > config('instance.timeline.network.cache_dropoff')) {
|
||||||
if(config('database.redis.client') === 'phpredis') {
|
|
||||||
Redis::zpopmin(self::CACHE_KEY);
|
Redis::zpopmin(self::CACHE_KEY);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Redis::zadd(self::CACHE_KEY, $val, $val);
|
return Redis::zadd(self::CACHE_KEY, $val, $val);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
class NotificationService {
|
class NotificationService {
|
||||||
|
|
||||||
const CACHE_KEY = 'pf:services:notifications:ids:';
|
const CACHE_KEY = 'pf:services:notifications:ids:';
|
||||||
|
const EPOCH_CACHE_KEY = 'pf:services:notifications:epoch-id:by-months:';
|
||||||
|
const ITEM_CACHE_TTL = 86400;
|
||||||
const MASTODON_TYPES = [
|
const MASTODON_TYPES = [
|
||||||
'follow',
|
'follow',
|
||||||
'follow_request',
|
'follow_request',
|
||||||
|
@ -44,11 +46,19 @@ class NotificationService {
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getEpochId($months = 6)
|
||||||
|
{
|
||||||
|
return Cache::remember(self::EPOCH_CACHE_KEY . $months, 1209600, function() use($months) {
|
||||||
|
return Notification::where('created_at', '>', now()->subMonths($months))->first()->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static function coldGet($id, $start = 0, $stop = 400)
|
public static function coldGet($id, $start = 0, $stop = 400)
|
||||||
{
|
{
|
||||||
$stop = $stop > 400 ? 400 : $stop;
|
$stop = $stop > 400 ? 400 : $stop;
|
||||||
$ids = Notification::whereProfileId($id)
|
$ids = Notification::where('id', '>', self::getEpochId())
|
||||||
->latest()
|
->where('profile_id', $id)
|
||||||
|
->orderByDesc('id')
|
||||||
->skip($start)
|
->skip($start)
|
||||||
->take($stop)
|
->take($stop)
|
||||||
->pluck('id');
|
->pluck('id');
|
||||||
|
@ -227,7 +237,7 @@ class NotificationService {
|
||||||
|
|
||||||
public static function getNotification($id)
|
public static function getNotification($id)
|
||||||
{
|
{
|
||||||
$notification = Cache::remember('service:notification:'.$id, 86400, function() use($id) {
|
$notification = Cache::remember('service:notification:'.$id, self::ITEM_CACHE_TTL, function() use($id) {
|
||||||
$n = Notification::with('item')->find($id);
|
$n = Notification::with('item')->find($id);
|
||||||
|
|
||||||
if(!$n) {
|
if(!$n) {
|
||||||
|
@ -259,7 +269,7 @@ class NotificationService {
|
||||||
|
|
||||||
public static function setNotification(Notification $notification)
|
public static function setNotification(Notification $notification)
|
||||||
{
|
{
|
||||||
return Cache::remember('service:notification:'.$notification->id, now()->addDays(3), function() use($notification) {
|
return Cache::remember('service:notification:'.$notification->id, self::ITEM_CACHE_TTL, function() use($notification) {
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
$resource = new Fractal\Resource\Item($notification, new NotificationTransformer());
|
$resource = new Fractal\Resource\Item($notification, new NotificationTransformer());
|
||||||
|
@ -270,8 +280,9 @@ class NotificationService {
|
||||||
public static function warmCache($id, $stop = 400, $force = false)
|
public static function warmCache($id, $stop = 400, $force = false)
|
||||||
{
|
{
|
||||||
if(self::count($id) == 0 || $force == true) {
|
if(self::count($id) == 0 || $force == true) {
|
||||||
$ids = Notification::whereProfileId($id)
|
$ids = Notification::where('profile_id', $id)
|
||||||
->latest()
|
->where('id', '>', self::getEpochId())
|
||||||
|
->orderByDesc('id')
|
||||||
->limit($stop)
|
->limit($stop)
|
||||||
->pluck('id');
|
->pluck('id');
|
||||||
foreach($ids as $key) {
|
foreach($ids as $key) {
|
||||||
|
|
|
@ -49,10 +49,8 @@ class PublicTimelineService {
|
||||||
public static function add($val)
|
public static function add($val)
|
||||||
{
|
{
|
||||||
if(self::count() > 400) {
|
if(self::count() > 400) {
|
||||||
if(config('database.redis.client') === 'phpredis') {
|
|
||||||
Redis::zpopmin(self::CACHE_KEY);
|
Redis::zpopmin(self::CACHE_KEY);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Redis::zadd(self::CACHE_KEY, $val, $val);
|
return Redis::zadd(self::CACHE_KEY, $val, $val);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +95,7 @@ class PublicTimelineService {
|
||||||
if(self::count() == 0 || $force == true) {
|
if(self::count() == 0 || $force == true) {
|
||||||
$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
|
$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
|
||||||
Redis::del(self::CACHE_KEY);
|
Redis::del(self::CACHE_KEY);
|
||||||
$minId = SnowflakeService::byDate(now()->subDays(14));
|
$minId = SnowflakeService::byDate(now()->subDays(90));
|
||||||
$ids = Status::where('id', '>', $minId)
|
$ids = Status::where('id', '>', $minId)
|
||||||
->whereNull(['uri', 'in_reply_to_id', 'reblog_of_id'])
|
->whereNull(['uri', 'in_reply_to_id', 'reblog_of_id'])
|
||||||
->when($hideNsfw, function($q, $hideNsfw) {
|
->when($hideNsfw, function($q, $hideNsfw) {
|
||||||
|
@ -107,9 +105,11 @@ class PublicTimelineService {
|
||||||
->whereScope('public')
|
->whereScope('public')
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->pluck('id');
|
->pluck('id', 'profile_id');
|
||||||
foreach($ids as $id) {
|
foreach($ids as $k => $id) {
|
||||||
|
if(AdminShadowFilterService::canAddToPublicFeedByProfileId($k)) {
|
||||||
self::add($id);
|
self::add($id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,14 @@ class RelationshipService
|
||||||
return self::get($aid, $tid);
|
return self::get($aid, $tid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function forget($aid, $tid)
|
||||||
|
{
|
||||||
|
Cache::forget('pf:services:follower:audience:' . $aid);
|
||||||
|
Cache::forget('pf:services:follower:audience:' . $tid);
|
||||||
|
self::delete($tid, $aid);
|
||||||
|
self::delete($aid, $tid);
|
||||||
|
}
|
||||||
|
|
||||||
public static function defaultRelation($tid)
|
public static function defaultRelation($tid)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
66
app/Services/ResilientMediaStorageService.php
Normal file
66
app/Services/ResilientMediaStorageService.php
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Storage;
|
||||||
|
use Illuminate\Http\File;
|
||||||
|
use Exception;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Aws\S3\Exception\S3Exception;
|
||||||
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
|
use League\Flysystem\UnableToWriteFile;
|
||||||
|
|
||||||
|
class ResilientMediaStorageService
|
||||||
|
{
|
||||||
|
static $attempts = 0;
|
||||||
|
|
||||||
|
public static function store($storagePath, $path, $name)
|
||||||
|
{
|
||||||
|
return (bool) config_cache('pixelfed.cloud_storage') && (bool) config('media.storage.remote.resilient_mode') ?
|
||||||
|
self::handleResilientStore($storagePath, $path, $name) :
|
||||||
|
self::handleStore($storagePath, $path, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function handleStore($storagePath, $path, $name)
|
||||||
|
{
|
||||||
|
return retry(3, function() use($storagePath, $path, $name) {
|
||||||
|
$baseDisk = (bool) config_cache('pixelfed.cloud_storage') ? config('filesystems.cloud') : 'local';
|
||||||
|
$disk = Storage::disk($baseDisk);
|
||||||
|
$file = $disk->putFileAs($storagePath, new File($path), $name, 'public');
|
||||||
|
return $disk->url($file);
|
||||||
|
}, random_int(100, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function handleResilientStore($storagePath, $path, $name)
|
||||||
|
{
|
||||||
|
$attempts = 0;
|
||||||
|
return retry(4, function() use($storagePath, $path, $name, $attempts) {
|
||||||
|
self::$attempts++;
|
||||||
|
usleep(100000);
|
||||||
|
$baseDisk = self::$attempts > 1 ? self::getAltDriver() : config('filesystems.cloud');
|
||||||
|
try {
|
||||||
|
$disk = Storage::disk($baseDisk);
|
||||||
|
$file = $disk->putFileAs($storagePath, new File($path), $name, 'public');
|
||||||
|
} catch (S3Exception | ClientException | ConnectException | UnableToWriteFile | Exception $e) {}
|
||||||
|
return $disk->url($file);
|
||||||
|
}, function (int $attempt, Exception $exception) {
|
||||||
|
return $attempt * 200;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAltDriver()
|
||||||
|
{
|
||||||
|
$drivers = [];
|
||||||
|
if(config('filesystems.disks.alt-primary.enabled')) {
|
||||||
|
$drivers[] = 'alt-primary';
|
||||||
|
}
|
||||||
|
if(config('filesystems.disks.alt-secondary.enabled')) {
|
||||||
|
$drivers[] = 'alt-secondary';
|
||||||
|
}
|
||||||
|
if(empty($drivers)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$key = array_rand($drivers, 1);
|
||||||
|
return $drivers[$key];
|
||||||
|
}
|
||||||
|
}
|
|
@ -179,8 +179,10 @@ class SearchApiV2Service
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
if(Helpers::validateLocalUrl($query)) {
|
if(Helpers::validateLocalUrl($query)) {
|
||||||
if(Str::contains($query, '/p/')) {
|
if(Str::contains($query, '/p/') || Str::contains($query, 'i/web/post/')) {
|
||||||
return $this->resolveLocalStatus();
|
return $this->resolveLocalStatus();
|
||||||
|
} else if(Str::contains($query, 'i/web/profile/')) {
|
||||||
|
return $this->resolveLocalProfileId();
|
||||||
} else {
|
} else {
|
||||||
return $this->resolveLocalProfile();
|
return $this->resolveLocalProfile();
|
||||||
}
|
}
|
||||||
|
@ -217,6 +219,14 @@ class SearchApiV2Service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($sid = Status::whereUri($query)->first()) {
|
||||||
|
$s = StatusService::get($sid->id, false);
|
||||||
|
if(in_array($s['visibility'], ['public', 'unlisted'])) {
|
||||||
|
$default['statuses'][] = $s;
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$res = ActivityPubFetchService::get($query);
|
$res = ActivityPubFetchService::get($query);
|
||||||
$banned = InstanceService::getBannedDomains();
|
$banned = InstanceService::getBannedDomains();
|
||||||
|
@ -238,11 +248,14 @@ class SearchApiV2Service
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
$note = $mastodonMode ?
|
$note = $mastodonMode ?
|
||||||
StatusService::getMastodon($obj['id']) :
|
StatusService::getMastodon($obj['id'], false) :
|
||||||
StatusService::get($obj['id']);
|
StatusService::get($obj['id'], false);
|
||||||
if(!$note) {
|
if(!$note) {
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
if(!isset($note['visibility']) || !in_array($note['visibility'], ['public', 'unlisted'])) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
$default['statuses'][] = $note;
|
$default['statuses'][] = $note;
|
||||||
return $default;
|
return $default;
|
||||||
break;
|
break;
|
||||||
|
@ -256,8 +269,8 @@ class SearchApiV2Service
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
$default['accounts'][] = $mastodonMode ?
|
$default['accounts'][] = $mastodonMode ?
|
||||||
AccountService::getMastodon($obj['id']) :
|
AccountService::getMastodon($obj['id'], true) :
|
||||||
AccountService::get($obj['id']);
|
AccountService::get($obj['id'], true);
|
||||||
return $default;
|
return $default;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -285,9 +298,9 @@ class SearchApiV2Service
|
||||||
protected function resolveLocalStatus()
|
protected function resolveLocalStatus()
|
||||||
{
|
{
|
||||||
$query = urldecode($this->query->input('q'));
|
$query = urldecode($this->query->input('q'));
|
||||||
$query = last(explode('/', $query));
|
$query = last(explode('/', parse_url($query, PHP_URL_PATH)));
|
||||||
$status = StatusService::getMastodon($query);
|
$status = StatusService::getMastodon($query, false);
|
||||||
if(!$status) {
|
if(!$status || !in_array($status['visibility'], ['public', 'unlisted'])) {
|
||||||
return [
|
return [
|
||||||
'accounts' => [],
|
'accounts' => [],
|
||||||
'hashtags' => [],
|
'hashtags' => [],
|
||||||
|
@ -307,7 +320,7 @@ class SearchApiV2Service
|
||||||
protected function resolveLocalProfile()
|
protected function resolveLocalProfile()
|
||||||
{
|
{
|
||||||
$query = urldecode($this->query->input('q'));
|
$query = urldecode($this->query->input('q'));
|
||||||
$query = last(explode('/', $query));
|
$query = last(explode('/', parse_url($query, PHP_URL_PATH)));
|
||||||
$profile = Profile::whereNull('status')
|
$profile = Profile::whereNull('status')
|
||||||
->whereNull('domain')
|
->whereNull('domain')
|
||||||
->whereUsername($query)
|
->whereUsername($query)
|
||||||
|
@ -325,7 +338,32 @@ class SearchApiV2Service
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||||
return [
|
return [
|
||||||
'accounts' => $fractal->createData($resource)->toArray(),
|
'accounts' => [$fractal->createData($resource)->toArray()],
|
||||||
|
'hashtags' => [],
|
||||||
|
'statuses' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveLocalProfileId()
|
||||||
|
{
|
||||||
|
$query = urldecode($this->query->input('q'));
|
||||||
|
$query = last(explode('/', parse_url($query, PHP_URL_PATH)));
|
||||||
|
$profile = Profile::whereNull('status')
|
||||||
|
->find($query);
|
||||||
|
|
||||||
|
if(!$profile) {
|
||||||
|
return [
|
||||||
|
'accounts' => [],
|
||||||
|
'hashtags' => [],
|
||||||
|
'statuses' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fractal = new Fractal\Manager();
|
||||||
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||||
|
return [
|
||||||
|
'accounts' => [$fractal->createData($resource)->toArray()],
|
||||||
'hashtags' => [],
|
'hashtags' => [],
|
||||||
'statuses' => []
|
'statuses' => []
|
||||||
];
|
];
|
||||||
|
|
|
@ -22,9 +22,9 @@ class StatusService
|
||||||
return self::CACHE_KEY . $p . $id;
|
return self::CACHE_KEY . $p . $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get($id, $publicOnly = true)
|
public static function get($id, $publicOnly = true, $mastodonMode = false)
|
||||||
{
|
{
|
||||||
return Cache::remember(self::key($id, $publicOnly), now()->addDays(7), function() use($id, $publicOnly) {
|
$res = Cache::remember(self::key($id, $publicOnly), 21600, function() use($id, $publicOnly) {
|
||||||
if($publicOnly) {
|
if($publicOnly) {
|
||||||
$status = Status::whereScope('public')->find($id);
|
$status = Status::whereScope('public')->find($id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -36,13 +36,23 @@ class StatusService
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
||||||
return $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
|
$res['_pid'] = isset($res['account']) && isset($res['account']['id']) ? $res['account']['id'] : null;
|
||||||
|
if(isset($res['_pid'])) {
|
||||||
|
unset($res['account']);
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
});
|
});
|
||||||
|
if($res && isset($res['_pid'])) {
|
||||||
|
$res['account'] = $mastodonMode === true ? AccountService::getMastodon($res['_pid'], true) : AccountService::get($res['_pid'], true);
|
||||||
|
unset($res['_pid']);
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getMastodon($id, $publicOnly = true)
|
public static function getMastodon($id, $publicOnly = true)
|
||||||
{
|
{
|
||||||
$status = self::get($id, $publicOnly);
|
$status = self::get($id, $publicOnly, true);
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -151,8 +161,6 @@ class StatusService
|
||||||
}
|
}
|
||||||
Cache::forget('status:transformer:media:attachments:' . $id);
|
Cache::forget('status:transformer:media:attachments:' . $id);
|
||||||
MediaService::del($id);
|
MediaService::del($id);
|
||||||
Cache::forget('status:thumb:nsfw0' . $id);
|
|
||||||
Cache::forget('status:thumb:nsfw1' . $id);
|
|
||||||
Cache::forget('pf:services:sh:id:' . $id);
|
Cache::forget('pf:services:sh:id:' . $id);
|
||||||
PublicTimelineService::rem($id);
|
PublicTimelineService::rem($id);
|
||||||
NetworkTimelineService::rem($id);
|
NetworkTimelineService::rem($id);
|
||||||
|
|
|
@ -9,7 +9,9 @@ use App\Http\Controllers\StatusController;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use App\Models\Poll;
|
use App\Models\Poll;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\Services\StatusService;
|
||||||
use App\Models\StatusEdit;
|
use App\Models\StatusEdit;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Status extends Model
|
class Status extends Model
|
||||||
{
|
{
|
||||||
|
@ -95,16 +97,30 @@ class Status extends Model
|
||||||
|
|
||||||
public function thumb($showNsfw = false)
|
public function thumb($showNsfw = false)
|
||||||
{
|
{
|
||||||
$key = $showNsfw ? 'status:thumb:nsfw1'.$this->id : 'status:thumb:nsfw0'.$this->id;
|
$entity = StatusService::get($this->id, false);
|
||||||
return Cache::remember($key, now()->addMinutes(15), function() use ($showNsfw) {
|
|
||||||
$type = $this->type ?? $this->setType();
|
if(!$entity || !isset($entity['media_attachments']) || empty($entity['media_attachments'])) {
|
||||||
$is_nsfw = !$showNsfw ? $this->is_nsfw : false;
|
|
||||||
if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['photo', 'photo:album', 'video'])) {
|
|
||||||
return url(Storage::url('public/no-preview.png'));
|
return url(Storage::url('public/no-preview.png'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return url(Storage::url($this->firstMedia()->thumbnail_path));
|
if((!isset($entity['sensitive']) || $entity['sensitive']) && !$showNsfw) {
|
||||||
});
|
return url(Storage::url('public/no-preview.png'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isset($entity['visibility']) || !in_array($entity['visibility'], ['public', 'unlisted'])) {
|
||||||
|
return url(Storage::url('public/no-preview.png'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect($entity['media_attachments'])
|
||||||
|
->filter(fn($media) => $media['type'] == 'image' && in_array($media['mime'], ['image/jpeg', 'image/png']))
|
||||||
|
->map(function($media) {
|
||||||
|
if(!Str::endsWith($media['preview_url'], ['no-preview.png', 'no-preview.jpg'])) {
|
||||||
|
return $media['preview_url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $media['url'];
|
||||||
|
})
|
||||||
|
->first() ?? url(Storage::url('public/no-preview.png'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function url($forceLocal = false)
|
public function url($forceLocal = false)
|
||||||
|
|
|
@ -4,17 +4,28 @@ namespace App\Transformer\ActivityPub;
|
||||||
|
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
|
||||||
class ProfileTransformer extends Fractal\TransformerAbstract
|
class ProfileTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
public function transform(Profile $profile)
|
public function transform(Profile $profile)
|
||||||
{
|
{
|
||||||
return [
|
$res = [
|
||||||
'@context' => [
|
'@context' => [
|
||||||
'https://w3id.org/security/v1',
|
'https://w3id.org/security/v1',
|
||||||
'https://www.w3.org/ns/activitystreams',
|
'https://www.w3.org/ns/activitystreams',
|
||||||
[
|
[
|
||||||
|
'toot' => 'http://joinmastodon.org/ns#',
|
||||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||||
|
'alsoKnownAs' => [
|
||||||
|
'@id' => 'as:alsoKnownAs',
|
||||||
|
'@type' => '@id'
|
||||||
|
],
|
||||||
|
'movedTo' => [
|
||||||
|
'@id' => 'as:movedTo',
|
||||||
|
'@type' => '@id'
|
||||||
|
],
|
||||||
|
'indexable' => 'toot:indexable',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'id' => $profile->permalink(),
|
'id' => $profile->permalink(),
|
||||||
|
@ -28,6 +39,7 @@ class ProfileTransformer extends Fractal\TransformerAbstract
|
||||||
'summary' => $profile->bio,
|
'summary' => $profile->bio,
|
||||||
'url' => $profile->url(),
|
'url' => $profile->url(),
|
||||||
'manuallyApprovesFollowers' => (bool) $profile->is_private,
|
'manuallyApprovesFollowers' => (bool) $profile->is_private,
|
||||||
|
'indexable' => (bool) $profile->indexable,
|
||||||
'publicKey' => [
|
'publicKey' => [
|
||||||
'id' => $profile->permalink().'#main-key',
|
'id' => $profile->permalink().'#main-key',
|
||||||
'owner' => $profile->permalink(),
|
'owner' => $profile->permalink(),
|
||||||
|
@ -42,5 +54,15 @@ class ProfileTransformer extends Fractal\TransformerAbstract
|
||||||
'sharedInbox' => config('app.url') . '/f/inbox'
|
'sharedInbox' => config('app.url') . '/f/inbox'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if($profile->aliases->count()) {
|
||||||
|
$res['alsoKnownAs'] = $profile->aliases->map(fn($alias) => $alias->uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($profile->moved_to_profile_id) {
|
||||||
|
$res['movedTo'] = AccountService::get($profile->moved_to_profile_id)['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,8 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
'@type' => '@id'
|
'@type' => '@id'
|
||||||
],
|
],
|
||||||
'toot' => 'http://joinmastodon.org/ns#',
|
'toot' => 'http://joinmastodon.org/ns#',
|
||||||
'Emoji' => 'toot:Emoji'
|
'Emoji' => 'toot:Emoji',
|
||||||
|
'blurhash' => 'toot:blurhash',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'id' => $status->permalink(),
|
'id' => $status->permalink(),
|
||||||
|
@ -103,12 +104,22 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
'cc' => $status->scopeToAudience('cc'),
|
'cc' => $status->scopeToAudience('cc'),
|
||||||
'sensitive' => (bool) $status->is_nsfw,
|
'sensitive' => (bool) $status->is_nsfw,
|
||||||
'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) {
|
'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) {
|
||||||
return [
|
$res = [
|
||||||
'type' => $media->activityVerb(),
|
'type' => $media->activityVerb(),
|
||||||
'mediaType' => $media->mime,
|
'mediaType' => $media->mime,
|
||||||
'url' => $media->url(),
|
'url' => $media->url(),
|
||||||
'name' => $media->caption,
|
'name' => $media->caption,
|
||||||
];
|
];
|
||||||
|
if($media->blurhash) {
|
||||||
|
$res['blurhash'] = $media->blurhash;
|
||||||
|
}
|
||||||
|
if($media->width) {
|
||||||
|
$res['width'] = $media->width;
|
||||||
|
}
|
||||||
|
if($media->height) {
|
||||||
|
$res['height'] = $media->height;
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
})->toArray(),
|
})->toArray(),
|
||||||
'tag' => $tags,
|
'tag' => $tags,
|
||||||
'commentsEnabled' => (bool) !$status->comments_disabled,
|
'commentsEnabled' => (bool) !$status->comments_disabled,
|
||||||
|
|
|
@ -82,7 +82,8 @@ class Note extends Fractal\TransformerAbstract
|
||||||
'@type' => '@id'
|
'@type' => '@id'
|
||||||
],
|
],
|
||||||
'toot' => 'http://joinmastodon.org/ns#',
|
'toot' => 'http://joinmastodon.org/ns#',
|
||||||
'Emoji' => 'toot:Emoji'
|
'Emoji' => 'toot:Emoji',
|
||||||
|
'blurhash' => 'toot:blurhash',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'id' => $status->url(),
|
'id' => $status->url(),
|
||||||
|
@ -97,12 +98,22 @@ class Note extends Fractal\TransformerAbstract
|
||||||
'cc' => $status->scopeToAudience('cc'),
|
'cc' => $status->scopeToAudience('cc'),
|
||||||
'sensitive' => (bool) $status->is_nsfw,
|
'sensitive' => (bool) $status->is_nsfw,
|
||||||
'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) {
|
'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) {
|
||||||
return [
|
$res = [
|
||||||
'type' => $media->activityVerb(),
|
'type' => $media->activityVerb(),
|
||||||
'mediaType' => $media->mime,
|
'mediaType' => $media->mime,
|
||||||
'url' => $media->url(),
|
'url' => $media->url(),
|
||||||
'name' => $media->caption,
|
'name' => $media->caption,
|
||||||
];
|
];
|
||||||
|
if($media->blurhash) {
|
||||||
|
$res['blurhash'] = $media->blurhash;
|
||||||
|
}
|
||||||
|
if($media->width) {
|
||||||
|
$res['width'] = $media->width;
|
||||||
|
}
|
||||||
|
if($media->height) {
|
||||||
|
$res['height'] = $media->height;
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
})->toArray(),
|
})->toArray(),
|
||||||
'tag' => $tags,
|
'tag' => $tags,
|
||||||
'commentsEnabled' => (bool) !$status->comments_disabled,
|
'commentsEnabled' => (bool) !$status->comments_disabled,
|
||||||
|
|
|
@ -33,7 +33,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
|
'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
|
||||||
'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null,
|
'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null,
|
||||||
'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null,
|
'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id, false) : null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $status->rendered ?? $status->caption,
|
||||||
'content_text' => $status->caption,
|
'content_text' => $status->caption,
|
||||||
'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)),
|
'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)),
|
||||||
|
|
12
app/User.php
12
app/User.php
|
@ -21,7 +21,8 @@ class User extends Authenticatable
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'deleted_at' => 'datetime',
|
'deleted_at' => 'datetime',
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'2fa_setup_at' => 'datetime'
|
'2fa_setup_at' => 'datetime',
|
||||||
|
'last_active_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +31,14 @@ class User extends Authenticatable
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name', 'username', 'email', 'password', 'app_register_ip'
|
'name',
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
'app_register_ip',
|
||||||
|
'email_verified_at',
|
||||||
|
'last_active_at',
|
||||||
|
'register_source'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,6 +40,7 @@ use App\Models\Poll;
|
||||||
use Illuminate\Contracts\Cache\LockTimeoutException;
|
use Illuminate\Contracts\Cache\LockTimeoutException;
|
||||||
use App\Jobs\ProfilePipeline\IncrementPostCount;
|
use App\Jobs\ProfilePipeline\IncrementPostCount;
|
||||||
use App\Jobs\ProfilePipeline\DecrementPostCount;
|
use App\Jobs\ProfilePipeline\DecrementPostCount;
|
||||||
|
use App\Services\DomainService;
|
||||||
use App\Services\UserFilterService;
|
use App\Services\UserFilterService;
|
||||||
|
|
||||||
class Helpers {
|
class Helpers {
|
||||||
|
@ -107,7 +108,10 @@ class Helpers {
|
||||||
'string',
|
'string',
|
||||||
Rule::in($mimeTypes)
|
Rule::in($mimeTypes)
|
||||||
],
|
],
|
||||||
'*.name' => 'sometimes|nullable|string'
|
'*.name' => 'sometimes|nullable|string',
|
||||||
|
'*.blurhash' => 'sometimes|nullable|string|min:6|max:164',
|
||||||
|
'*.width' => 'sometimes|nullable|integer|min:1|max:5000',
|
||||||
|
'*.height' => 'sometimes|nullable|integer|min:1|max:5000',
|
||||||
])->passes();
|
])->passes();
|
||||||
|
|
||||||
return $valid;
|
return $valid;
|
||||||
|
@ -168,17 +172,24 @@ class Helpers {
|
||||||
|
|
||||||
$hash = hash('sha256', $url);
|
$hash = hash('sha256', $url);
|
||||||
$key = "helpers:url:valid:sha256-{$hash}";
|
$key = "helpers:url:valid:sha256-{$hash}";
|
||||||
$ttl = now()->addMinutes(5);
|
|
||||||
|
|
||||||
$valid = Cache::remember($key, $ttl, function() use($url) {
|
$valid = Cache::remember($key, 900, function() use($url) {
|
||||||
$localhosts = [
|
$localhosts = [
|
||||||
'127.0.0.1', 'localhost', '::1'
|
'127.0.0.1', 'localhost', '::1'
|
||||||
];
|
];
|
||||||
|
|
||||||
if(mb_substr($url, 0, 8) !== 'https://') {
|
if(strtolower(mb_substr($url, 0, 8)) !== 'https://') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(substr_count($url, '://') !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mb_substr($url, 0, 8) !== 'https://') {
|
||||||
|
$url = 'https://' . substr($url, 8);
|
||||||
|
}
|
||||||
|
|
||||||
$valid = filter_var($url, FILTER_VALIDATE_URL);
|
$valid = filter_var($url, FILTER_VALIDATE_URL);
|
||||||
|
|
||||||
if(!$valid) {
|
if(!$valid) {
|
||||||
|
@ -187,15 +198,12 @@ class Helpers {
|
||||||
|
|
||||||
$host = parse_url($valid, PHP_URL_HOST);
|
$host = parse_url($valid, PHP_URL_HOST);
|
||||||
|
|
||||||
// if(count(dns_get_record($host, DNS_A | DNS_AAAA)) == 0) {
|
if(in_array($host, $localhosts)) {
|
||||||
// return false;
|
return false;
|
||||||
// }
|
}
|
||||||
|
|
||||||
if(config('costar.enabled') == true) {
|
if(config('security.url.verify_dns')) {
|
||||||
if(
|
if(DomainService::hasValidDns($host) === false) {
|
||||||
(config('costar.domain.block') != null && Str::contains($host, config('costar.domain.block')) == true) ||
|
|
||||||
(config('costar.actor.block') != null && in_array($url, config('costar.actor.block')) == true)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,11 +215,6 @@ class Helpers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(in_array($host, $localhosts)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -224,7 +227,7 @@ class Helpers {
|
||||||
if($url == true) {
|
if($url == true) {
|
||||||
$domain = config('pixelfed.domain.app');
|
$domain = config('pixelfed.domain.app');
|
||||||
$host = parse_url($url, PHP_URL_HOST);
|
$host = parse_url($url, PHP_URL_HOST);
|
||||||
$url = $domain === $host ? $url : false;
|
$url = strtolower($domain) === strtolower($host) ? $url : false;
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -276,7 +279,7 @@ class Helpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_array($val)) {
|
if(is_array($val)) {
|
||||||
return !empty($val) ? $val[0] : null;
|
return !empty($val) ? head($val) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -466,12 +469,19 @@ class Helpers {
|
||||||
$scope = self::getScope($activity, $url);
|
$scope = self::getScope($activity, $url);
|
||||||
$cw = self::getSensitive($activity, $url);
|
$cw = self::getSensitive($activity, $url);
|
||||||
$pid = is_object($profile) ? $profile->id : (is_array($profile) ? $profile['id'] : null);
|
$pid = is_object($profile) ? $profile->id : (is_array($profile) ? $profile['id'] : null);
|
||||||
|
$isUnlisted = is_object($profile) ? $profile->unlisted : (is_array($profile) ? $profile['unlisted'] : false);
|
||||||
$commentsDisabled = isset($activity['commentsEnabled']) ? !boolval($activity['commentsEnabled']) : false;
|
$commentsDisabled = isset($activity['commentsEnabled']) ? !boolval($activity['commentsEnabled']) : false;
|
||||||
|
|
||||||
if(!$pid) {
|
if(!$pid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($scope == 'public') {
|
||||||
|
if($isUnlisted == true) {
|
||||||
|
$scope = 'unlisted';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$status = Status::updateOrCreate(
|
$status = Status::updateOrCreate(
|
||||||
[
|
[
|
||||||
'uri' => $url
|
'uri' => $url
|
||||||
|
@ -519,9 +529,11 @@ class Helpers {
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
if(!in_array($urlDomain, $filteredDomains)) {
|
if(!in_array($urlDomain, $filteredDomains)) {
|
||||||
|
if(!$isUnlisted) {
|
||||||
NetworkTimelineService::add($status->id);
|
NetworkTimelineService::add($status->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IncrementPostCount::dispatch($pid)->onQueue('low');
|
IncrementPostCount::dispatch($pid)->onQueue('low');
|
||||||
|
|
||||||
|
@ -675,6 +687,8 @@ class Helpers {
|
||||||
$blurhash = isset($media['blurhash']) ? $media['blurhash'] : null;
|
$blurhash = isset($media['blurhash']) ? $media['blurhash'] : null;
|
||||||
$license = isset($media['license']) ? License::nameToId($media['license']) : null;
|
$license = isset($media['license']) ? License::nameToId($media['license']) : null;
|
||||||
$caption = isset($media['name']) ? Purify::clean($media['name']) : null;
|
$caption = isset($media['name']) ? Purify::clean($media['name']) : null;
|
||||||
|
$width = isset($media['width']) ? $media['width'] : false;
|
||||||
|
$height = isset($media['height']) ? $media['height'] : false;
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media();
|
||||||
$media->blurhash = $blurhash;
|
$media->blurhash = $blurhash;
|
||||||
|
@ -686,6 +700,12 @@ class Helpers {
|
||||||
$media->remote_url = $url;
|
$media->remote_url = $url;
|
||||||
$media->caption = $caption;
|
$media->caption = $caption;
|
||||||
$media->order = $key + 1;
|
$media->order = $key + 1;
|
||||||
|
if($width) {
|
||||||
|
$media->width = $width;
|
||||||
|
}
|
||||||
|
if($height) {
|
||||||
|
$media->height = $height;
|
||||||
|
}
|
||||||
if($license) {
|
if($license) {
|
||||||
$media->license = $license;
|
$media->license = $license;
|
||||||
}
|
}
|
||||||
|
@ -776,6 +796,7 @@ class Helpers {
|
||||||
'inbox_url' => $res['inbox'],
|
'inbox_url' => $res['inbox'],
|
||||||
'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null,
|
'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null,
|
||||||
'public_key' => $res['publicKey']['publicKeyPem'],
|
'public_key' => $res['publicKey']['publicKeyPem'],
|
||||||
|
'indexable' => isset($res['indexable']) && is_bool($res['indexable']) ? $res['indexable'] : false,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ use Illuminate\Support\Str;
|
||||||
use App\Jobs\LikePipeline\LikePipeline;
|
use App\Jobs\LikePipeline\LikePipeline;
|
||||||
use App\Jobs\FollowPipeline\FollowPipeline;
|
use App\Jobs\FollowPipeline\FollowPipeline;
|
||||||
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||||
use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
use App\Jobs\StoryPipeline\StoryExpire;
|
use App\Jobs\StoryPipeline\StoryExpire;
|
||||||
use App\Jobs\StoryPipeline\StoryFetch;
|
use App\Jobs\StoryPipeline\StoryFetch;
|
||||||
use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline;
|
use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline;
|
||||||
|
@ -281,7 +281,8 @@ class Inbox
|
||||||
}
|
}
|
||||||
|
|
||||||
if($actor->followers_count == 0) {
|
if($actor->followers_count == 0) {
|
||||||
if(FollowerService::followerCount($actor->id, true) == 0) {
|
if(config('federation.activitypub.ingest.store_notes_without_followers')) {
|
||||||
|
} else if(FollowerService::followerCount($actor->id, true) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -706,7 +707,7 @@ class Inbox
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DeleteRemoteStatusPipeline::dispatch($status)->onQueue('high');
|
RemoteStatusDelete::dispatch($status)->onQueue('high');
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,9 @@ class Blurhash {
|
||||||
$pixels[] = $row;
|
$pixels[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Free the allocated GdImage object from memory:
|
||||||
|
imagedestroy($image);
|
||||||
|
|
||||||
$components_x = 4;
|
$components_x = 4;
|
||||||
$components_y = 4;
|
$components_y = 4;
|
||||||
$blurhash = BlurhashEngine::encode($pixels, $components_x, $components_y);
|
$blurhash = BlurhashEngine::encode($pixels, $components_x, $components_y);
|
||||||
|
|
75
composer.lock
generated
75
composer.lock
generated
|
@ -62,16 +62,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "aws/aws-sdk-php",
|
"name": "aws/aws-sdk-php",
|
||||||
"version": "3.275.5",
|
"version": "3.275.7",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||||
"reference": "d46961b82e857f77059c0c78160719ecb26f6cc6"
|
"reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d46961b82e857f77059c0c78160719ecb26f6cc6",
|
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
|
||||||
"reference": "d46961b82e857f77059c0c78160719ecb26f6cc6",
|
"reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -151,9 +151,9 @@
|
||||||
"support": {
|
"support": {
|
||||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.275.5"
|
"source": "https://github.com/aws/aws-sdk-php/tree/3.275.7"
|
||||||
},
|
},
|
||||||
"time": "2023-07-07T18:20:11+00:00"
|
"time": "2023-07-13T18:21:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
|
@ -2357,16 +2357,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
"version": "v10.14.1",
|
"version": "v10.15.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/framework.git",
|
"url": "https://github.com/laravel/framework.git",
|
||||||
"reference": "6f89a2b74b232d8bf2e1d9ed87e311841263dfcb"
|
"reference": "c7599dc92e04532824bafbd226c2936ce6a905b8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/framework/zipball/6f89a2b74b232d8bf2e1d9ed87e311841263dfcb",
|
"url": "https://api.github.com/repos/laravel/framework/zipball/c7599dc92e04532824bafbd226c2936ce6a905b8",
|
||||||
"reference": "6f89a2b74b232d8bf2e1d9ed87e311841263dfcb",
|
"reference": "c7599dc92e04532824bafbd226c2936ce6a905b8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2553,7 +2553,7 @@
|
||||||
"issues": "https://github.com/laravel/framework/issues",
|
"issues": "https://github.com/laravel/framework/issues",
|
||||||
"source": "https://github.com/laravel/framework"
|
"source": "https://github.com/laravel/framework"
|
||||||
},
|
},
|
||||||
"time": "2023-06-28T14:25:16+00:00"
|
"time": "2023-07-11T13:43:52+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/helpers",
|
"name": "laravel/helpers",
|
||||||
|
@ -2613,16 +2613,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/horizon",
|
"name": "laravel/horizon",
|
||||||
"version": "v5.17.0",
|
"version": "v5.18.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/horizon.git",
|
"url": "https://github.com/laravel/horizon.git",
|
||||||
"reference": "569c7154033679a1ca05b43bfa640cc60aa3b37b"
|
"reference": "b14498a09af826035e46ae8d6b013d0ec849bdb7"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/horizon/zipball/569c7154033679a1ca05b43bfa640cc60aa3b37b",
|
"url": "https://api.github.com/repos/laravel/horizon/zipball/b14498a09af826035e46ae8d6b013d0ec849bdb7",
|
||||||
"reference": "569c7154033679a1ca05b43bfa640cc60aa3b37b",
|
"reference": "b14498a09af826035e46ae8d6b013d0ec849bdb7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2685,9 +2685,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/laravel/horizon/issues",
|
"issues": "https://github.com/laravel/horizon/issues",
|
||||||
"source": "https://github.com/laravel/horizon/tree/v5.17.0"
|
"source": "https://github.com/laravel/horizon/tree/v5.18.0"
|
||||||
},
|
},
|
||||||
"time": "2023-06-13T20:49:30+00:00"
|
"time": "2023-06-30T15:11:51+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/passport",
|
"name": "laravel/passport",
|
||||||
|
@ -6651,23 +6651,24 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "react/promise",
|
"name": "react/promise",
|
||||||
"version": "v2.10.0",
|
"version": "v3.0.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/reactphp/promise.git",
|
"url": "https://github.com/reactphp/promise.git",
|
||||||
"reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38"
|
"reference": "c86753c76fd3be465d93b308f18d189f01a22be4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
|
"url": "https://api.github.com/repos/reactphp/promise/zipball/c86753c76fd3be465d93b308f18d189f01a22be4",
|
||||||
"reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
|
"reference": "c86753c76fd3be465d93b308f18d189f01a22be4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.4.0"
|
"php": ">=7.1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36"
|
"phpstan/phpstan": "1.10.20 || 1.4.10",
|
||||||
|
"phpunit/phpunit": "^9.5 || ^7.5"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -6711,7 +6712,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/reactphp/promise/issues",
|
"issues": "https://github.com/reactphp/promise/issues",
|
||||||
"source": "https://github.com/reactphp/promise/tree/v2.10.0"
|
"source": "https://github.com/reactphp/promise/tree/v3.0.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -6719,7 +6720,7 @@
|
||||||
"type": "open_collective"
|
"type": "open_collective"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-05-02T15:15:43+00:00"
|
"time": "2023-07-11T16:12:49+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "react/socket",
|
"name": "react/socket",
|
||||||
|
@ -10920,16 +10921,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filp/whoops",
|
"name": "filp/whoops",
|
||||||
"version": "2.15.2",
|
"version": "2.15.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filp/whoops.git",
|
"url": "https://github.com/filp/whoops.git",
|
||||||
"reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73"
|
"reference": "c83e88a30524f9360b11f585f71e6b17313b7187"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filp/whoops/zipball/aac9304c5ed61bf7b1b7a6064bf9806ab842ce73",
|
"url": "https://api.github.com/repos/filp/whoops/zipball/c83e88a30524f9360b11f585f71e6b17313b7187",
|
||||||
"reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73",
|
"reference": "c83e88a30524f9360b11f585f71e6b17313b7187",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -10979,7 +10980,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/filp/whoops/issues",
|
"issues": "https://github.com/filp/whoops/issues",
|
||||||
"source": "https://github.com/filp/whoops/tree/2.15.2"
|
"source": "https://github.com/filp/whoops/tree/2.15.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -10987,7 +10988,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-04-12T12:00:00+00:00"
|
"time": "2023-07-13T12:00:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hamcrest/hamcrest-php",
|
"name": "hamcrest/hamcrest-php",
|
||||||
|
@ -11101,16 +11102,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/telescope",
|
"name": "laravel/telescope",
|
||||||
"version": "v4.15.0",
|
"version": "v4.15.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/telescope.git",
|
"url": "https://github.com/laravel/telescope.git",
|
||||||
"reference": "572a19b4c9b09295848de9a2352737a756a0fb05"
|
"reference": "5d74ae4c9f269b756d7877ad1527770c59846e14"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/telescope/zipball/572a19b4c9b09295848de9a2352737a756a0fb05",
|
"url": "https://api.github.com/repos/laravel/telescope/zipball/5d74ae4c9f269b756d7877ad1527770c59846e14",
|
||||||
"reference": "572a19b4c9b09295848de9a2352737a756a0fb05",
|
"reference": "5d74ae4c9f269b756d7877ad1527770c59846e14",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -11166,9 +11167,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/laravel/telescope/issues",
|
"issues": "https://github.com/laravel/telescope/issues",
|
||||||
"source": "https://github.com/laravel/telescope/tree/v4.15.0"
|
"source": "https://github.com/laravel/telescope/tree/v4.15.2"
|
||||||
},
|
},
|
||||||
"time": "2023-06-08T13:57:22+00:00"
|
"time": "2023-07-13T20:06:27+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mockery/mockery",
|
"name": "mockery/mockery",
|
||||||
|
|
|
@ -25,7 +25,11 @@ return [
|
||||||
'enabled' => env('AP_LOGGER_ENABLED', false),
|
'enabled' => env('AP_LOGGER_ENABLED', false),
|
||||||
'driver' => 'log'
|
'driver' => 'log'
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
|
||||||
|
'ingest' => [
|
||||||
|
'store_notes_without_followers' => env('AP_INGEST_STORE_NOTES_WITHOUT_FOLLOWERS', false),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'atom' => [
|
'atom' => [
|
||||||
|
@ -52,6 +56,5 @@ return [
|
||||||
|
|
||||||
// max size in bytes, default is 2mb
|
// max size in bytes, default is 2mb
|
||||||
'max_size' => env('CUSTOM_EMOJI_MAX_SIZE', 2000000),
|
'max_size' => env('CUSTOM_EMOJI_MAX_SIZE', 2000000),
|
||||||
]
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -79,6 +79,34 @@ return [
|
||||||
'throw' => true,
|
'throw' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'alt-primary' => [
|
||||||
|
'enabled' => env('ALT_PRI_ENABLED', false),
|
||||||
|
'driver' => 's3',
|
||||||
|
'key' => env('ALT_PRI_AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('ALT_PRI_AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('ALT_PRI_AWS_DEFAULT_REGION'),
|
||||||
|
'bucket' => env('ALT_PRI_AWS_BUCKET'),
|
||||||
|
'visibility' => 'public',
|
||||||
|
'url' => env('ALT_PRI_AWS_URL'),
|
||||||
|
'endpoint' => env('ALT_PRI_AWS_ENDPOINT'),
|
||||||
|
'use_path_style_endpoint' => env('ALT_PRI_AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||||
|
'throw' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'alt-secondary' => [
|
||||||
|
'enabled' => env('ALT_SEC_ENABLED', false),
|
||||||
|
'driver' => 's3',
|
||||||
|
'key' => env('ALT_SEC_AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('ALT_SEC_AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('ALT_SEC_AWS_DEFAULT_REGION'),
|
||||||
|
'bucket' => env('ALT_SEC_AWS_BUCKET'),
|
||||||
|
'visibility' => 'public',
|
||||||
|
'url' => env('ALT_SEC_AWS_URL'),
|
||||||
|
'endpoint' => env('ALT_SEC_AWS_ENDPOINT'),
|
||||||
|
'use_path_style_endpoint' => env('ALT_SEC_AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||||
|
'throw' => true,
|
||||||
|
],
|
||||||
|
|
||||||
'spaces' => [
|
'spaces' => [
|
||||||
'driver' => 's3',
|
'driver' => 's3',
|
||||||
'key' => env('DO_SPACES_KEY'),
|
'key' => env('DO_SPACES_KEY'),
|
||||||
|
|
|
@ -18,7 +18,9 @@ return [
|
||||||
| Disabled by default.
|
| Disabled by default.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'cloud' => env('MEDIA_REMOTE_STORE_CLOUD', false)
|
'cloud' => env('MEDIA_REMOTE_STORE_CLOUD', false),
|
||||||
|
|
||||||
|
'resilient_mode' => env('ALT_PRI_ENABLED', false) || env('ALT_SEC_ENABLED', false),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
|
@ -23,7 +23,7 @@ return [
|
||||||
| This value is the version of your Pixelfed instance.
|
| This value is the version of your Pixelfed instance.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'version' => '0.11.8',
|
'version' => '0.11.9',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
57
config/remote-auth.php
Normal file
57
config/remote-auth.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'mastodon' => [
|
||||||
|
'enabled' => env('PF_LOGIN_WITH_MASTODON_ENABLED', false),
|
||||||
|
'ignore_closed_state' => env('PF_LOGIN_WITH_MASTODON_ENABLED_SKIP_CLOSED', false),
|
||||||
|
|
||||||
|
'contraints' => [
|
||||||
|
/*
|
||||||
|
* Skip email verification
|
||||||
|
*
|
||||||
|
* To improve the onboarding experience, you can opt to skip the email
|
||||||
|
* verification process and automatically verify their email
|
||||||
|
*/
|
||||||
|
'skip_email_verification' => env('PF_LOGIN_WITH_MASTODON_SKIP_EMAIL', true),
|
||||||
|
],
|
||||||
|
|
||||||
|
'domains' => [
|
||||||
|
'default' => 'mastodon.social,mastodon.online,mstdn.social,mas.to',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom mastodon domains
|
||||||
|
*
|
||||||
|
* Define a comma separated list of custom domains to allow
|
||||||
|
*/
|
||||||
|
'custom' => env('PF_LOGIN_WITH_MASTODON_DOMAINS'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use only default domains
|
||||||
|
*
|
||||||
|
* Allow Sign-in with Mastodon using only the default domains
|
||||||
|
*/
|
||||||
|
'only_default' => env('PF_LOGIN_WITH_MASTODON_ONLY_DEFAULT', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use only custom domains
|
||||||
|
*
|
||||||
|
* Allow Sign-in with Mastodon using only the custom domains
|
||||||
|
* you define, in comma separated format
|
||||||
|
*/
|
||||||
|
'only_custom' => env('PF_LOGIN_WITH_MASTODON_ONLY_CUSTOM', false),
|
||||||
|
],
|
||||||
|
|
||||||
|
'max_uses' => [
|
||||||
|
/*
|
||||||
|
* Max Uses
|
||||||
|
*
|
||||||
|
* Using a centralized service operated by pixelfed.org that tracks mastodon imports,
|
||||||
|
* you can set a limit of how many times a mastodon account can be imported across
|
||||||
|
* all known and reporting Pixelfed instances to prevent the same masto account from
|
||||||
|
* abusing this
|
||||||
|
*/
|
||||||
|
'enabled' => env('PF_LOGIN_WITH_MASTODON_ENFORCE_MAX_USES', true),
|
||||||
|
'limit' => env('PF_LOGIN_WITH_MASTODON_MAX_USES_LIMIT', 3)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
9
config/security.php
Normal file
9
config/security.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'url' => [
|
||||||
|
'verify_dns' => env('PF_SECURITY_URL_VERIFY_DNS', false),
|
||||||
|
|
||||||
|
'trusted_domains' => env('PF_SECURITY_URL_TRUSTED_DOMAINS', 'pixelfed.social,pixelfed.art,mastodon.social'),
|
||||||
|
]
|
||||||
|
];
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateGroupsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('groups', function (Blueprint $table) {
|
||||||
|
$table->bigInteger('id')->unsigned()->primary();
|
||||||
|
$table->bigInteger('profile_id')->unsigned()->nullable()->index();
|
||||||
|
$table->string('status')->nullable()->index();
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->text('description')->nullable();
|
||||||
|
$table->text('rules')->nullable();
|
||||||
|
$table->boolean('local')->default(true)->index();
|
||||||
|
$table->string('remote_url')->nullable();
|
||||||
|
$table->string('inbox_url')->nullable();
|
||||||
|
$table->boolean('is_private')->default(false);
|
||||||
|
$table->boolean('local_only')->default(false);
|
||||||
|
$table->json('metadata')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('groups');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateGroupMembersTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('group_members', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->bigInteger('group_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('profile_id')->unsigned()->index();
|
||||||
|
$table->string('role')->default('member')->index();
|
||||||
|
$table->boolean('local_group')->default(false)->index();
|
||||||
|
$table->boolean('local_profile')->default(false)->index();
|
||||||
|
$table->boolean('join_request')->default(false)->index();
|
||||||
|
$table->timestamp('approved_at')->nullable();
|
||||||
|
$table->timestamp('rejected_at')->nullable();
|
||||||
|
$table->unique(['group_id', 'profile_id']);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('group_members');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateGroupPostsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('group_posts', function (Blueprint $table) {
|
||||||
|
$table->bigInteger('id')->unsigned()->primary();
|
||||||
|
$table->bigInteger('group_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('profile_id')->unsigned()->nullable()->index();
|
||||||
|
$table->string('type')->nullable()->index();
|
||||||
|
$table->bigInteger('status_id')->unsigned()->unique();
|
||||||
|
$table->string('remote_url')->unique()->nullable()->index();
|
||||||
|
$table->bigInteger('reply_child_id')->unsigned()->nullable();
|
||||||
|
$table->bigInteger('in_reply_to_id')->unsigned()->nullable();
|
||||||
|
$table->bigInteger('reblog_of_id')->unsigned()->nullable();
|
||||||
|
$table->unsignedInteger('reply_count')->nullable();
|
||||||
|
$table->string('status')->nullable()->index();
|
||||||
|
$table->json('metadata')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('group_posts');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateGroupInvitationsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('group_invitations', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('group_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('from_profile_id')->unsigned()->index();
|
||||||
|
$table->bigInteger('to_profile_id')->unsigned()->index();
|
||||||
|
$table->string('role')->nullable();
|
||||||
|
$table->boolean('to_local')->default(true)->index();
|
||||||
|
$table->boolean('from_local')->default(true)->index();
|
||||||
|
$table->unique(['group_id', 'to_profile_id']);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('group_invitations');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('remote_auths', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('software')->nullable();
|
||||||
|
$table->string('domain')->nullable()->index();
|
||||||
|
$table->string('webfinger')->nullable()->unique()->index();
|
||||||
|
$table->unsignedInteger('instance_id')->nullable()->index();
|
||||||
|
$table->unsignedInteger('user_id')->nullable()->unique()->index();
|
||||||
|
$table->unsignedInteger('client_id')->nullable()->index();
|
||||||
|
$table->string('ip_address')->nullable();
|
||||||
|
$table->text('bearer_token')->nullable();
|
||||||
|
$table->json('verify_credentials')->nullable();
|
||||||
|
$table->timestamp('last_successful_login_at')->nullable();
|
||||||
|
$table->timestamp('last_verify_credentials_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('remote_auths');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('remote_auth_instances', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('domain')->nullable()->unique()->index();
|
||||||
|
$table->unsignedInteger('instance_id')->nullable()->index();
|
||||||
|
$table->string('client_id')->nullable();
|
||||||
|
$table->string('client_secret')->nullable();
|
||||||
|
$table->string('redirect_uri')->nullable();
|
||||||
|
$table->string('root_domain')->nullable()->index();
|
||||||
|
$table->boolean('allowed')->nullable()->index();
|
||||||
|
$table->boolean('banned')->default(false)->index();
|
||||||
|
$table->boolean('active')->default(true)->index();
|
||||||
|
$table->timestamp('last_refreshed_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('remote_auth_instances');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('followers', function (Blueprint $table) {
|
||||||
|
$table->boolean('show_reblogs')->default(true)->index()->after('local_following');
|
||||||
|
$table->boolean('notify')->default(false)->index()->after('show_reblogs');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('followers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('show_reblogs');
|
||||||
|
$table->dropColumn('notify');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('profile_aliases', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('profile_id')->nullable()->index();
|
||||||
|
$table->string('acct')->nullable();
|
||||||
|
$table->string('uri')->nullable();
|
||||||
|
$table->foreign('profile_id')->references('id')->on('profiles');
|
||||||
|
$table->unique(['profile_id', 'acct'], 'profile_id_acct_unique');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('profile_aliases');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('moved_to_profile_id')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('moved_to_profile_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->boolean('indexable')->default(false)->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('indexable');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('admin_shadow_filters', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('admin_id')->nullable();
|
||||||
|
$table->morphs('item');
|
||||||
|
$table->boolean('is_local')->default(true)->index();
|
||||||
|
$table->text('note')->nullable();
|
||||||
|
$table->boolean('active')->default(false)->index();
|
||||||
|
$table->json('history')->nullable();
|
||||||
|
$table->json('ruleset')->nullable();
|
||||||
|
$table->boolean('prevent_ap_fanout')->default(false)->index();
|
||||||
|
$table->boolean('prevent_new_dms')->default(false)->index();
|
||||||
|
$table->boolean('ignore_reports')->default(false)->index();
|
||||||
|
$table->boolean('ignore_mentions')->default(false)->index();
|
||||||
|
$table->boolean('ignore_links')->default(false)->index();
|
||||||
|
$table->boolean('ignore_hashtags')->default(false)->index();
|
||||||
|
$table->boolean('hide_from_public_feeds')->default(false)->index();
|
||||||
|
$table->boolean('hide_from_tag_feeds')->default(false)->index();
|
||||||
|
$table->boolean('hide_embeds')->default(false)->index();
|
||||||
|
$table->boolean('hide_from_story_carousel')->default(false)->index();
|
||||||
|
$table->boolean('hide_from_search_autocomplete')->default(false)->index();
|
||||||
|
$table->boolean('hide_from_search')->default(false)->index();
|
||||||
|
$table->boolean('requires_login')->default(false)->index();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('admin_shadow_filters');
|
||||||
|
}
|
||||||
|
};
|
BIN
public/css/spa.css
vendored
BIN
public/css/spa.css
vendored
Binary file not shown.
BIN
public/js/daci.chunk.914d307d69fcfcd4.js
vendored
BIN
public/js/daci.chunk.914d307d69fcfcd4.js
vendored
Binary file not shown.
BIN
public/js/daci.chunk.bfa9e4f459fec835.js
vendored
Normal file
BIN
public/js/daci.chunk.bfa9e4f459fec835.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~findfriends.chunk.6bd4ddbabd979778.js
vendored
Normal file
BIN
public/js/discover~findfriends.chunk.6bd4ddbabd979778.js
vendored
Normal file
Binary file not shown.
BIN
public/js/discover~memories.chunk.400f9f019bdb9fdf.js
vendored
Normal file
BIN
public/js/discover~memories.chunk.400f9f019bdb9fdf.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~myhashtags.chunk.ee5af357937cad2f.js
vendored
Normal file
BIN
public/js/discover~myhashtags.chunk.ee5af357937cad2f.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~serverfeed.chunk.fbe31eedcdafc87e.js
vendored
Normal file
BIN
public/js/discover~serverfeed.chunk.fbe31eedcdafc87e.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~settings.chunk.909aa0316f43235e.js
vendored
Normal file
BIN
public/js/discover~settings.chunk.909aa0316f43235e.js
vendored
Normal file
Binary file not shown.
BIN
public/js/home.chunk.2d93b527d492e6de.js
vendored
BIN
public/js/home.chunk.2d93b527d492e6de.js
vendored
Binary file not shown.
BIN
public/js/home.chunk.bd623a430a5584c2.js
vendored
Normal file
BIN
public/js/home.chunk.bd623a430a5584c2.js
vendored
Normal file
Binary file not shown.
BIN
public/js/landing.js
vendored
BIN
public/js/landing.js
vendored
Binary file not shown.
BIN
public/js/manifest.js
vendored
BIN
public/js/manifest.js
vendored
Binary file not shown.
BIN
public/js/post.chunk.729ca668f46545cb.js
vendored
Normal file
BIN
public/js/post.chunk.729ca668f46545cb.js
vendored
Normal file
Binary file not shown.
BIN
public/js/post.chunk.cd535334efc77c34.js
vendored
BIN
public/js/post.chunk.cd535334efc77c34.js
vendored
Binary file not shown.
BIN
public/js/profile.chunk.029572d9018fc65f.js
vendored
Normal file
BIN
public/js/profile.chunk.029572d9018fc65f.js
vendored
Normal file
Binary file not shown.
BIN
public/js/profile.chunk.4049e1eecea398ee.js
vendored
BIN
public/js/profile.chunk.4049e1eecea398ee.js
vendored
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue