mirror of https://github.com/pixelfed/pixelfed
merge dev
commit
7b3e11012f
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\User;
|
||||
use App\Models\DefaultDomainBlock;
|
||||
use App\Models\UserDomainBlock;
|
||||
use function Laravel\Prompts\text;
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\progress;
|
||||
|
||||
class AddUserDomainBlock extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:add-user-domain-block';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Apply a domain block to all users';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$domain = text('Enter domain you want to block');
|
||||
$domain = strtolower($domain);
|
||||
$domain = $this->validateDomain($domain);
|
||||
if(!$domain || empty($domain)) {
|
||||
$this->error('Invalid domain');
|
||||
return;
|
||||
}
|
||||
$this->processBlocks($domain);
|
||||
return;
|
||||
}
|
||||
|
||||
protected function validateDomain($domain)
|
||||
{
|
||||
if(!strpos($domain, '.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(str_starts_with($domain, 'https://')) {
|
||||
$domain = str_replace('https://', '', $domain);
|
||||
}
|
||||
|
||||
if(str_starts_with($domain, 'http://')) {
|
||||
$domain = str_replace('http://', '', $domain);
|
||||
}
|
||||
|
||||
$domain = strtolower(parse_url('https://' . $domain, PHP_URL_HOST));
|
||||
|
||||
$valid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME|FILTER_NULL_ON_FAILURE);
|
||||
if(!$valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if($domain === config('pixelfed.domain.app')) {
|
||||
$this->error('Invalid domain');
|
||||
return;
|
||||
}
|
||||
|
||||
$confirmed = confirm('Are you sure you want to block ' . $domain . '?');
|
||||
if(!$confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
protected function processBlocks($domain)
|
||||
{
|
||||
DefaultDomainBlock::updateOrCreate([
|
||||
'domain' => $domain
|
||||
]);
|
||||
progress(
|
||||
label: 'Updating user domain blocks...',
|
||||
steps: User::lazyById(500),
|
||||
callback: fn ($user) => $this->performTask($user, $domain),
|
||||
);
|
||||
}
|
||||
|
||||
protected function performTask($user, $domain)
|
||||
{
|
||||
if(!$user->profile_id || $user->delete_after) {
|
||||
return;
|
||||
}
|
||||
|
||||
if($user->status != null && $user->status != 'disabled') {
|
||||
return;
|
||||
}
|
||||
|
||||
UserDomainBlock::updateOrCreate([
|
||||
'profile_id' => $user->profile_id,
|
||||
'domain' => $domain
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Cache;
|
||||
use Storage;
|
||||
use App\Avatar;
|
||||
use App\Jobs\AvatarPipeline\AvatarStorageCleanup;
|
||||
|
||||
class AvatarStorageDeepClean extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'avatar:storage-deep-clean';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Cleanup avatar storage';
|
||||
|
||||
protected $shouldKeepRunning = true;
|
||||
protected $counter = 0;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->info(' ____ _ ______ __ ');
|
||||
$this->info(' / __ \(_) _____ / / __/__ ____/ / ');
|
||||
$this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / ');
|
||||
$this->info(' / ____/ /> </ __/ / __/ __/ /_/ / ');
|
||||
$this->info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ ');
|
||||
$this->info(' ');
|
||||
$this->info(' Pixelfed Avatar Deep Cleaner');
|
||||
$this->line(' ');
|
||||
$this->info(' Purge/delete old and outdated avatars from remote accounts');
|
||||
$this->line(' ');
|
||||
|
||||
$storage = [
|
||||
'cloud' => boolval(config_cache('pixelfed.cloud_storage')),
|
||||
'local' => boolval(config_cache('federation.avatars.store_local'))
|
||||
];
|
||||
|
||||
if(!$storage['cloud'] && !$storage['local']) {
|
||||
$this->error('Remote avatars are not cached locally, there is nothing to purge. Aborting...');
|
||||
exit;
|
||||
}
|
||||
|
||||
$start = 0;
|
||||
|
||||
if(!$this->confirm('Are you sure you want to proceed?')) {
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
}
|
||||
|
||||
if(!$this->activeCheck()) {
|
||||
$this->info('Found existing deep cleaning job');
|
||||
if(!$this->confirm('Do you want to continue where you left off?')) {
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
} else {
|
||||
$start = Cache::has('cmd:asdp') ? (int) Cache::get('cmd:asdp') : (int) Storage::get('avatar-deep-clean.json');
|
||||
|
||||
if($start && $start < 1 || $start > PHP_INT_MAX) {
|
||||
$this->error('Error fetching cached value');
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$count = Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->count();
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
|
||||
foreach(Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->lazyById(10, 'id') as $avatar) {
|
||||
usleep(random_int(50, 1000));
|
||||
$this->counter++;
|
||||
$this->handleAvatar($avatar);
|
||||
$bar->advance();
|
||||
}
|
||||
$bar->finish();
|
||||
}
|
||||
|
||||
protected function updateCache($id)
|
||||
{
|
||||
Cache::put('cmd:asdp', $id);
|
||||
if($this->counter % 5 === 0) {
|
||||
Storage::put('avatar-deep-clean.json', $id);
|
||||
}
|
||||
}
|
||||
|
||||
protected function activeCheck()
|
||||
{
|
||||
if(Storage::exists('avatar-deep-clean.json') || Cache::has('cmd:asdp')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function handleAvatar($avatar)
|
||||
{
|
||||
$this->updateCache($avatar->id);
|
||||
$queues = ['feed', 'mmo', 'feed', 'mmo', 'feed', 'feed', 'mmo', 'low'];
|
||||
$queue = $queues[random_int(0, 7)];
|
||||
AvatarStorageCleanup::dispatch($avatar)->onQueue($queue);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\User;
|
||||
use App\Models\DefaultDomainBlock;
|
||||
use App\Models\UserDomainBlock;
|
||||
use function Laravel\Prompts\text;
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\progress;
|
||||
|
||||
class DeleteUserDomainBlock extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:delete-user-domain-block';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Remove a domain block for all users';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$domain = text('Enter domain you want to unblock');
|
||||
$domain = strtolower($domain);
|
||||
$domain = $this->validateDomain($domain);
|
||||
if(!$domain || empty($domain)) {
|
||||
$this->error('Invalid domain');
|
||||
return;
|
||||
}
|
||||
$this->processUnblocks($domain);
|
||||
return;
|
||||
}
|
||||
|
||||
protected function validateDomain($domain)
|
||||
{
|
||||
if(!strpos($domain, '.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(str_starts_with($domain, 'https://')) {
|
||||
$domain = str_replace('https://', '', $domain);
|
||||
}
|
||||
|
||||
if(str_starts_with($domain, 'http://')) {
|
||||
$domain = str_replace('http://', '', $domain);
|
||||
}
|
||||
|
||||
$domain = strtolower(parse_url('https://' . $domain, PHP_URL_HOST));
|
||||
|
||||
$valid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME|FILTER_NULL_ON_FAILURE);
|
||||
if(!$valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if($domain === config('pixelfed.domain.app')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$confirmed = confirm('Are you sure you want to unblock ' . $domain . '?');
|
||||
if(!$confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
protected function processUnblocks($domain)
|
||||
{
|
||||
DefaultDomainBlock::whereDomain($domain)->delete();
|
||||
if(!UserDomainBlock::whereDomain($domain)->count()) {
|
||||
$this->info('No results found!');
|
||||
return;
|
||||
}
|
||||
progress(
|
||||
label: 'Updating user domain blocks...',
|
||||
steps: UserDomainBlock::whereDomain($domain)->lazyById(500),
|
||||
callback: fn ($domainBlock) => $this->performTask($domainBlock),
|
||||
);
|
||||
}
|
||||
|
||||
protected function performTask($domainBlock)
|
||||
{
|
||||
$domainBlock->deleteQuietly();
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Hashtag;
|
||||
use App\StatusHashtag;
|
||||
use DB;
|
||||
|
||||
class HashtagCachedCountUpdate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:hashtag-cached-count-update {--limit=100}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update cached counter of hashtags';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$limit = $this->option('limit');
|
||||
$tags = Hashtag::whereNull('cached_count')->limit($limit)->get();
|
||||
$count = count($tags);
|
||||
if(!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
$bar->start();
|
||||
|
||||
foreach($tags as $tag) {
|
||||
$count = DB::table('status_hashtags')->whereHashtagId($tag->id)->count();
|
||||
if(!$count) {
|
||||
$tag->cached_count = 0;
|
||||
$tag->saveQuietly();
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
$tag->cached_count = $count;
|
||||
$tag->saveQuietly();
|
||||
$bar->advance();
|
||||
}
|
||||
$bar->finish();
|
||||
$this->line(' ');
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Hashtag;
|
||||
use App\StatusHashtag;
|
||||
use App\Models\HashtagRelated;
|
||||
use App\Services\HashtagRelatedService;
|
||||
use Illuminate\Contracts\Console\PromptsForMissingInput;
|
||||
use function Laravel\Prompts\multiselect;
|
||||
use function Laravel\Prompts\confirm;
|
||||
|
||||
class HashtagRelatedGenerate extends Command implements PromptsForMissingInput
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:hashtag-related-generate {tag}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Prompt for missing input arguments using the returned questions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function promptForMissingArgumentsUsing()
|
||||
{
|
||||
return [
|
||||
'tag' => 'Which hashtag should we generate related tags for?',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$tag = $this->argument('tag');
|
||||
$hashtag = Hashtag::whereName($tag)->orWhere('slug', $tag)->first();
|
||||
if(!$hashtag) {
|
||||
$this->error('Hashtag not found, aborting...');
|
||||
exit;
|
||||
}
|
||||
|
||||
$exists = HashtagRelated::whereHashtagId($hashtag->id)->exists();
|
||||
|
||||
if($exists) {
|
||||
$confirmed = confirm('Found existing related tags, do you want to regenerate them?');
|
||||
if(!$confirmed) {
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Looking up #' . $tag . '...');
|
||||
|
||||
$tags = StatusHashtag::whereHashtagId($hashtag->id)->count();
|
||||
if(!$tags || $tags < 100) {
|
||||
$this->error('Not enough posts found to generate related hashtags!');
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->info('Found ' . $tags . ' posts that use that hashtag');
|
||||
$related = collect(HashtagRelatedService::fetchRelatedTags($tag));
|
||||
|
||||
$selected = multiselect(
|
||||
label: 'Which tags do you want to generate?',
|
||||
options: $related->pluck('name'),
|
||||
required: true,
|
||||
);
|
||||
|
||||
$filtered = $related->filter(fn($i) => in_array($i['name'], $selected))->all();
|
||||
$agg_score = $related->filter(fn($i) => in_array($i['name'], $selected))->sum('related_count');
|
||||
|
||||
HashtagRelated::updateOrCreate([
|
||||
'hashtag_id' => $hashtag->id,
|
||||
], [
|
||||
'related_tags' => array_values($filtered),
|
||||
'agg_score' => $agg_score,
|
||||
'last_calculated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info('Finished!');
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Media;
|
||||
use Cache, Storage;
|
||||
use Illuminate\Contracts\Console\PromptsForMissingInput;
|
||||
|
||||
class MediaCloudUrlRewrite extends Command implements PromptsForMissingInput
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'media:cloud-url-rewrite {oldDomain} {newDomain}';
|
||||
|
||||
/**
|
||||
* Prompt for missing input arguments using the returned questions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function promptForMissingArgumentsUsing()
|
||||
{
|
||||
return [
|
||||
'oldDomain' => 'The old S3 domain',
|
||||
'newDomain' => 'The new S3 domain'
|
||||
];
|
||||
}
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rewrite S3 media urls from local users';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->preflightCheck();
|
||||
$this->bootMessage();
|
||||
$this->confirmCloudUrl();
|
||||
}
|
||||
|
||||
protected function preflightCheck()
|
||||
{
|
||||
if(config_cache('pixelfed.cloud_storage') != true) {
|
||||
$this->info('Error: Cloud storage is not enabled!');
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
protected function bootMessage()
|
||||
{
|
||||
$this->info(' ____ _ ______ __ ');
|
||||
$this->info(' / __ \(_) _____ / / __/__ ____/ / ');
|
||||
$this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / ');
|
||||
$this->info(' / ____/ /> </ __/ / __/ __/ /_/ / ');
|
||||
$this->info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ ');
|
||||
$this->info(' ');
|
||||
$this->info(' Media Cloud Url Rewrite Tool');
|
||||
$this->info(' ===');
|
||||
$this->info(' Old S3: ' . trim($this->argument('oldDomain')));
|
||||
$this->info(' New S3: ' . trim($this->argument('newDomain')));
|
||||
$this->info(' ');
|
||||
}
|
||||
|
||||
protected function confirmCloudUrl()
|
||||
{
|
||||
$disk = Storage::disk(config('filesystems.cloud'))->url('test');
|
||||
$domain = parse_url($disk, PHP_URL_HOST);
|
||||
if(trim($this->argument('newDomain')) !== $domain) {
|
||||
$this->error('Error: The new S3 domain you entered is not currently configured');
|
||||
exit;
|
||||
}
|
||||
|
||||
if(!$this->confirm('Confirm this is correct')) {
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->updateUrls();
|
||||
}
|
||||
|
||||
protected function updateUrls()
|
||||
{
|
||||
$this->info('Updating urls...');
|
||||
$oldDomain = trim($this->argument('oldDomain'));
|
||||
$newDomain = trim($this->argument('newDomain'));
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
$count = Media::whereNotNull('cdn_url')->count();
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
$counter = 0;
|
||||
$bar->start();
|
||||
foreach(Media::whereNotNull('cdn_url')->lazyById(1000, 'id') as $media) {
|
||||
if(strncmp($media->media_path, 'http', 4) === 0) {
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
$cdnHost = parse_url($media->cdn_url, PHP_URL_HOST);
|
||||
if($oldDomain != $cdnHost || $newDomain == $cdnHost) {
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
$media->cdn_url = str_replace($oldDomain, $newDomain, $media->cdn_url);
|
||||
|
||||
if($media->thumbnail_url != null) {
|
||||
$thumbHost = parse_url($media->thumbnail_url, PHP_URL_HOST);
|
||||
if($thumbHost == $oldDomain) {
|
||||
$thumbUrl = $disk->url($media->thumbnail_path);
|
||||
$media->thumbnail_url = $thumbUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if($media->optimized_url != null) {
|
||||
$optiHost = parse_url($media->optimized_url, PHP_URL_HOST);
|
||||
if($optiHost == $oldDomain) {
|
||||
$optiUrl = str_replace($oldDomain, $newDomain, $media->optimized_url);
|
||||
$media->optimized_url = $optiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
$media->save();
|
||||
$counter++;
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
|
||||
$this->line(' ');
|
||||
$this->info('Finished! Updated ' . $counter . ' total records!');
|
||||
$this->line(' ');
|
||||
$this->info('Tip: Run `php artisan cache:clear` to purge cached urls');
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\InternalPipeline\NotificationEpochUpdatePipeline;
|
||||
|
||||
class NotificationEpochUpdate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:notification-epoch-update';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update notification epoch';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
NotificationEpochUpdatePipeline::dispatch();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\PromptsForMissingInput;
|
||||
use App\User;
|
||||
|
||||
class UserToggle2FA extends Command implements PromptsForMissingInput
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'user:2fa {username}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Disable two factor authentication for given username';
|
||||
|
||||
/**
|
||||
* Prompt for missing input arguments using the returned questions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function promptForMissingArgumentsUsing()
|
||||
{
|
||||
return [
|
||||
'username' => 'Which username should we disable 2FA for?',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$user = User::whereUsername($this->argument('username'))->first();
|
||||
|
||||
if(!$user) {
|
||||
$this->error('Could not find any user with that username');
|
||||
exit;
|
||||
}
|
||||
|
||||
if(!$user->{'2fa_enabled'}) {
|
||||
$this->info('User did not have 2FA enabled!');
|
||||
return;
|
||||
}
|
||||
|
||||
$user->{'2fa_enabled'} = false;
|
||||
$user->{'2fa_secret'} = null;
|
||||
$user->{'2fa_backup_codes'} = null;
|
||||
$user->save();
|
||||
|
||||
$this->info('Successfully disabled 2FA on this account!');
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
<?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::whereHas('profile')
|
||||
->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');
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\UserDomainBlock;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Services\UserFilterService;
|
||||
use Illuminate\Bus\Batch;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Jobs\HomeFeedPipeline\FeedRemoveDomainPipeline;
|
||||
use App\Jobs\ProfilePipeline\ProfilePurgeNotificationsByDomain;
|
||||
use App\Jobs\ProfilePipeline\ProfilePurgeFollowersByDomain;
|
||||
|
||||
class DomainBlockController extends Controller
|
||||
{
|
||||
public function json($res, $code = 200, $headers = [])
|
||||
{
|
||||
return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
abort_unless($request->user(), 403);
|
||||
$this->validate($request, [
|
||||
'limit' => 'sometimes|integer|min:1|max:200'
|
||||
]);
|
||||
$limit = $request->input('limit', 100);
|
||||
$id = $request->user()->profile_id;
|
||||
$filters = UserDomainBlock::whereProfileId($id)->orderByDesc('id')->cursorPaginate($limit);
|
||||
$links = null;
|
||||
$headers = [];
|
||||
|
||||
if($filters->nextCursor()) {
|
||||
$links .= '<'.$filters->nextPageUrl().'&limit='.$limit.'>; rel="next"';
|
||||
}
|
||||
|
||||
if($filters->previousCursor()) {
|
||||
if($links != null) {
|
||||
$links .= ', ';
|
||||
}
|
||||
$links .= '<'.$filters->previousPageUrl().'&limit='.$limit.'>; rel="prev"';
|
||||
}
|
||||
|
||||
if($links) {
|
||||
$headers = ['Link' => $links];
|
||||
}
|
||||
return $this->json($filters->pluck('domain'), 200, $headers);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
abort_unless($request->user(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'domain' => 'required|active_url|min:1|max:120'
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
$domain = trim($request->input('domain'));
|
||||
|
||||
if(Helpers::validateUrl($domain) == false) {
|
||||
return abort(500, 'Invalid domain or already blocked by server admins');
|
||||
}
|
||||
|
||||
$domain = strtolower(parse_url($domain, PHP_URL_HOST));
|
||||
|
||||
abort_if(config_cache('pixelfed.domain.app') == $domain, 400, 'Cannot ban your own server');
|
||||
|
||||
$existingCount = UserDomainBlock::whereProfileId($pid)->count();
|
||||
$maxLimit = config('instance.user_filters.max_domain_blocks');
|
||||
$errorMsg = __('profile.block.domain.max', ['max' => $maxLimit]);
|
||||
|
||||
abort_if($existingCount >= $maxLimit, 400, $errorMsg);
|
||||
|
||||
$block = UserDomainBlock::updateOrCreate([
|
||||
'profile_id' => $pid,
|
||||
'domain' => $domain
|
||||
]);
|
||||
|
||||
if($block->wasRecentlyCreated) {
|
||||
Bus::batch([
|
||||
[
|
||||
new FeedRemoveDomainPipeline($pid, $domain),
|
||||
new ProfilePurgeNotificationsByDomain($pid, $domain),
|
||||
new ProfilePurgeFollowersByDomain($pid, $domain)
|
||||
]
|
||||
])->allowFailures()->onQueue('feed')->dispatch();
|
||||
|
||||
Cache::forget('profile:following:' . $pid);
|
||||
UserFilterService::domainBlocks($pid, true);
|
||||
}
|
||||
|
||||
return $this->json([]);
|
||||
}
|
||||
|
||||
public function delete(Request $request)
|
||||
{
|
||||
abort_unless($request->user(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'domain' => 'required|min:1|max:120'
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
$domain = strtolower(trim($request->input('domain')));
|
||||
|
||||
$filters = UserDomainBlock::whereProfileId($pid)->whereDomain($domain)->delete();
|
||||
|
||||
UserFilterService::domainBlocks($pid, true);
|
||||
|
||||
return $this->json([]);
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Hashtag;
|
||||
use App\HashtagFollow;
|
||||
use App\StatusHashtag;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\HashtagService;
|
||||
use App\Services\HashtagFollowService;
|
||||
use App\Services\HashtagRelatedService;
|
||||
use App\Http\Resources\MastoApi\FollowedTagResource;
|
||||
use App\Jobs\HomeFeedPipeline\FeedWarmCachePipeline;
|
||||
use App\Jobs\HomeFeedPipeline\HashtagUnfollowPipeline;
|
||||
|
||||
class TagsController extends Controller
|
||||
{
|
||||
const PF_API_ENTITY_KEY = "_pe";
|
||||
|
||||
public function json($res, $code = 200, $headers = [])
|
||||
{
|
||||
return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/tags/:id/related
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function relatedTags(Request $request, $tag)
|
||||
{
|
||||
abort_unless($request->user(), 403);
|
||||
$tag = Hashtag::whereSlug($tag)->firstOrFail();
|
||||
return HashtagRelatedService::get($tag->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/tags/:id/follow
|
||||
*
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function followHashtag(Request $request, $id)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$account = AccountService::get($pid);
|
||||
|
||||
$operator = config('database.default') == 'pgsql' ? 'ilike' : 'like';
|
||||
$tag = Hashtag::where('name', $operator, $id)
|
||||
->orWhere('slug', $operator, $id)
|
||||
->first();
|
||||
|
||||
abort_if(!$tag, 422, 'Unknown hashtag');
|
||||
|
||||
abort_if(
|
||||
HashtagFollow::whereProfileId($pid)->count() >= HashtagFollow::MAX_LIMIT,
|
||||
422,
|
||||
'You cannot follow more than ' . HashtagFollow::MAX_LIMIT . ' hashtags.'
|
||||
);
|
||||
|
||||
$follows = HashtagFollow::updateOrCreate(
|
||||
[
|
||||
'profile_id' => $account['id'],
|
||||
'hashtag_id' => $tag->id
|
||||
],
|
||||
[
|
||||
'user_id' => $request->user()->id
|
||||
]
|
||||
);
|
||||
|
||||
HashtagService::follow($pid, $tag->id);
|
||||
HashtagFollowService::add($tag->id, $pid);
|
||||
|
||||
return response()->json(FollowedTagResource::make($follows)->toArray($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/tags/:id/unfollow
|
||||
*
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function unfollowHashtag(Request $request, $id)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$account = AccountService::get($pid);
|
||||
|
||||
$operator = config('database.default') == 'pgsql' ? 'ilike' : 'like';
|
||||
$tag = Hashtag::where('name', $operator, $id)
|
||||
->orWhere('slug', $operator, $id)
|
||||
->first();
|
||||
|
||||
abort_if(!$tag, 422, 'Unknown hashtag');
|
||||
|
||||
$follows = HashtagFollow::whereProfileId($pid)
|
||||
->whereHashtagId($tag->id)
|
||||
->first();
|
||||
|
||||
if(!$follows) {
|
||||
return [
|
||||
'name' => $tag->name,
|
||||
'url' => config('app.url') . '/i/web/hashtag/' . $tag->slug,
|
||||
'history' => [],
|
||||
'following' => false
|
||||
];
|
||||
}
|
||||
|
||||
if($follows) {
|
||||
HashtagService::unfollow($pid, $tag->id);
|
||||
HashtagFollowService::unfollow($tag->id, $pid);
|
||||
HashtagUnfollowPipeline::dispatch($tag->id, $pid, $tag->slug)->onQueue('feed');
|
||||
$follows->delete();
|
||||
}
|
||||
|
||||
$res = FollowedTagResource::make($follows)->toArray($request);
|
||||
$res['following'] = false;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/tags/:id
|
||||
*
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getHashtag(Request $request, $id)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$account = AccountService::get($pid);
|
||||
$operator = config('database.default') == 'pgsql' ? 'ilike' : 'like';
|
||||
$tag = Hashtag::where('name', $operator, $id)
|
||||
->orWhere('slug', $operator, $id)
|
||||
->first();
|
||||
|
||||
if(!$tag) {
|
||||
return [
|
||||
'name' => $id,
|
||||
'url' => config('app.url') . '/i/web/hashtag/' . $id,
|
||||
'history' => [],
|
||||
'following' => false
|
||||
];
|
||||
}
|
||||
|
||||
$res = [
|
||||
'name' => $tag->name,
|
||||
'url' => config('app.url') . '/i/web/hashtag/' . $tag->slug,
|
||||
'history' => [],
|
||||
'following' => HashtagService::isFollowing($pid, $tag->id)
|
||||
];
|
||||
|
||||
if($request->has(self::PF_API_ENTITY_KEY)) {
|
||||
$res['count'] = HashtagService::count($tag->id);
|
||||
}
|
||||
|
||||
return $this->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/followed_tags
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFollowedTags(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$account = AccountService::get($request->user()->profile_id);
|
||||
|
||||
$this->validate($request, [
|
||||
'cursor' => 'sometimes',
|
||||
'limit' => 'sometimes|integer|min:1|max:200'
|
||||
]);
|
||||
$limit = $request->input('limit', 100);
|
||||
|
||||
$res = HashtagFollow::whereProfileId($account['id'])
|
||||
->orderByDesc('id')
|
||||
->cursorPaginate($limit)
|
||||
->withQueryString();
|
||||
|
||||
$pagination = false;
|
||||
$prevPage = $res->nextPageUrl();
|
||||
$nextPage = $res->previousPageUrl();
|
||||
if($nextPage && $prevPage) {
|
||||
$pagination = '<' . $nextPage . '>; rel="next", <' . $prevPage . '>; rel="prev"';
|
||||
} else if($nextPage && !$prevPage) {
|
||||
$pagination = '<' . $nextPage . '>; rel="next"';
|
||||
} else if(!$nextPage && $prevPage) {
|
||||
$pagination = '<' . $prevPage . '>; rel="prev"';
|
||||
}
|
||||
|
||||
if($pagination) {
|
||||
return response()->json(FollowedTagResource::collection($res)->collection)
|
||||
->header('Link', $pagination);
|
||||
}
|
||||
return response()->json(FollowedTagResource::collection($res)->collection);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\AvatarPipeline;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use App\Services\AvatarService;
|
||||
use App\Avatar;
|
||||
|
||||
class AvatarStorageCleanup implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $avatar;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 3;
|
||||
public $timeout = 900;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'avatar:storage:cleanup:' . $this->avatar->profile_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("avatar-storage-cleanup:{$this->avatar->profile_id}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(Avatar $avatar)
|
||||
{
|
||||
$this->avatar = $avatar->withoutRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
AvatarService::cleanup($this->avatar, true);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\AvatarPipeline;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use App\Services\AvatarService;
|
||||
use App\Avatar;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AvatarStorageLargePurge implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $avatar;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 3;
|
||||
public $timeout = 900;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'avatar:storage:lg-purge:' . $this->avatar->profile_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("avatar-storage-purge:{$this->avatar->profile_id}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(Avatar $avatar)
|
||||
{
|
||||
$this->avatar = $avatar->withoutRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$avatar = $this->avatar;
|
||||
|
||||
$disk = AvatarService::disk();
|
||||
|
||||
$files = collect(AvatarService::storage($avatar));
|
||||
|
||||
$curFile = Str::of($avatar->cdn_url)->explode('/')->last();
|
||||
|
||||
$files = $files->filter(function($f) use($curFile) {
|
||||
return !$curFile || !str_ends_with($f, $curFile);
|
||||
})->each(function($name) use($disk) {
|
||||
$disk->delete($name);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\DirectPipeline;
|
||||
|
||||
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\Util\ActivityPub\Helpers;
|
||||
|
||||
class DirectDeletePipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
|
||||
protected $profile;
|
||||
protected $url;
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($profile, $url, $payload)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->url = $url;
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Helpers::sendSignedObject($this->profile, $this->url, $this->payload);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\DirectPipeline;
|
||||
|
||||
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\Util\ActivityPub\Helpers;
|
||||
|
||||
class DirectDeliverPipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
|
||||
protected $profile;
|
||||
protected $url;
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($profile, $url, $payload)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->url = $url;
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Helpers::sendSignedObject($this->profile, $this->url, $this->payload);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\HomeTimelineService;
|
||||
use App\Services\SnowflakeService;
|
||||
use App\Status;
|
||||
|
||||
class FeedFollowPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $actorId;
|
||||
protected $followingId;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hts:feed:insert:follows:aid:' . $this->actorId . ':fid:' . $this->followingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hts:feed:insert:follows:aid:{$this->actorId}:fid:{$this->followingId}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($actorId, $followingId)
|
||||
{
|
||||
$this->actorId = $actorId;
|
||||
$this->followingId = $followingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$actorId = $this->actorId;
|
||||
$followingId = $this->followingId;
|
||||
|
||||
$minId = SnowflakeService::byDate(now()->subWeeks(6));
|
||||
|
||||
$ids = Status::where('id', '>', $minId)
|
||||
->where('profile_id', $followingId)
|
||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||
->whereIn('visibility',['public', 'unlisted', 'private'])
|
||||
->orderByDesc('id')
|
||||
->limit(HomeTimelineService::FOLLOWER_FEED_POST_LIMIT)
|
||||
->pluck('id');
|
||||
|
||||
foreach($ids as $id) {
|
||||
HomeTimelineService::add($actorId, $id);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\UserFilter;
|
||||
use App\Models\UserDomainBlock;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\HomeTimelineService;
|
||||
use App\Services\StatusService;
|
||||
|
||||
class FeedInsertPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $sid;
|
||||
protected $pid;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hts:feed:insert:sid:' . $this->sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hts:feed:insert:sid:{$this->sid}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($sid, $pid)
|
||||
{
|
||||
$this->sid = $sid;
|
||||
$this->pid = $pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$sid = $this->sid;
|
||||
$status = StatusService::get($sid, false);
|
||||
|
||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
HomeTimelineService::add($this->pid, $this->sid);
|
||||
|
||||
$ids = FollowerService::localFollowerIds($this->pid);
|
||||
|
||||
if(!$ids || !count($ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||
$skipIds = [];
|
||||
|
||||
if(strtolower(config('pixelfed.domain.app')) !== $domain) {
|
||||
$skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray();
|
||||
}
|
||||
|
||||
$filters = UserFilter::whereFilterableType('App\Profile')
|
||||
->whereFilterableId($status['account']['id'])
|
||||
->whereIn('filter_type', ['mute', 'block'])
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
|
||||
if($filters && count($filters)) {
|
||||
$skipIds = array_merge($skipIds, $filters);
|
||||
}
|
||||
|
||||
$skipIds = array_unique(array_values($skipIds));
|
||||
|
||||
foreach($ids as $id) {
|
||||
if(!in_array($id, $skipIds)) {
|
||||
HomeTimelineService::add($id, $this->sid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\UserFilter;
|
||||
use App\Models\UserDomainBlock;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\HomeTimelineService;
|
||||
use App\Services\StatusService;
|
||||
|
||||
class FeedInsertRemotePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $sid;
|
||||
protected $pid;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hts:feed:insert:remote:sid:' . $this->sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hts:feed:insert:remote:sid:{$this->sid}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($sid, $pid)
|
||||
{
|
||||
$this->sid = $sid;
|
||||
$this->pid = $pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$sid = $this->sid;
|
||||
$status = StatusService::get($sid, false);
|
||||
|
||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ids = FollowerService::localFollowerIds($this->pid);
|
||||
|
||||
if(!$ids || !count($ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||
$skipIds = [];
|
||||
|
||||
if(strtolower(config('pixelfed.domain.app')) !== $domain) {
|
||||
$skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray();
|
||||
}
|
||||
|
||||
$filters = UserFilter::whereFilterableType('App\Profile')
|
||||
->whereFilterableId($status['account']['id'])
|
||||
->whereIn('filter_type', ['mute', 'block'])
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
|
||||
if($filters && count($filters)) {
|
||||
$skipIds = array_merge($skipIds, $filters);
|
||||
}
|
||||
|
||||
$skipIds = array_unique(array_values($skipIds));
|
||||
|
||||
foreach($ids as $id) {
|
||||
if(!in_array($id, $skipIds)) {
|
||||
HomeTimelineService::add($id, $this->sid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
use Illuminate\Bus\Batchable;
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\HomeTimelineService;
|
||||
|
||||
class FeedRemoveDomainPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $pid;
|
||||
protected $domain;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hts:feed:remove:domain:' . $this->pid . ':d-' . $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hts:feed:remove:domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($pid, $domain)
|
||||
{
|
||||
$this->pid = $pid;
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if(!config('exp.cached_home_timeline')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->batch()->cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!$this->pid || !$this->domain) {
|
||||
return;
|
||||
}
|
||||
$domain = strtolower($this->domain);
|
||||
$pid = $this->pid;
|
||||
$posts = HomeTimelineService::get($pid, '0', '-1');
|
||||
|
||||
foreach($posts as $post) {
|
||||
$status = StatusService::get($post, false);
|
||||
if(!$status || !isset($status['url'])) {
|
||||
HomeTimelineService::rem($pid, $post);
|
||||
continue;
|
||||
}
|
||||
$host = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||
if($host === strtolower(config('pixelfed.domain.app')) || !$host) {
|
||||
continue;
|
||||
}
|
||||
if($host === $domain) {
|
||||
HomeTimelineService::rem($pid, $status['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\HomeTimelineService;
|
||||
|
||||
class FeedRemovePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $sid;
|
||||
protected $pid;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hts:feed:remove:sid:' . $this->sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hts:feed:remove:sid:{$this->sid}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($sid, $pid)
|
||||
{
|
||||
$this->sid = $sid;
|
||||
$this->pid = $pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$ids = FollowerService::localFollowerIds($this->pid);
|
||||
|
||||
HomeTimelineService::rem($this->pid, $this->sid);
|
||||
|
||||
foreach($ids as $id) {
|
||||
HomeTimelineService::rem($id, $this->sid);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\HomeTimelineService;
|
||||
|
||||
class FeedRemoveRemotePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $sid;
|
||||
protected $pid;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hts:feed:remove:remote:sid:' . $this->sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hts:feed:remove:remote:sid:{$this->sid}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($sid, $pid)
|
||||
{
|
||||
$this->sid = $sid;
|
||||
$this->pid = $pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$ids = FollowerService::localFollowerIds($this->pid);
|
||||
|
||||
foreach($ids as $id) {
|
||||
HomeTimelineService::rem($id, $this->sid);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\HomeTimelineService;
|
||||
|
||||
class FeedUnfollowPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $actorId;
|
||||
protected $followingId;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hts:feed:remove:follows:aid:' . $this->actorId . ':fid:' . $this->followingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hts:feed:remove:follows:aid:{$this->actorId}:fid:{$this->followingId}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($actorId, $followingId)
|
||||
{
|
||||
$this->actorId = $actorId;
|
||||
$this->followingId = $followingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$actorId = $this->actorId;
|
||||
$followingId = $this->followingId;
|
||||
|
||||
$ids = HomeTimelineService::get($actorId, 0, -1);
|
||||
foreach($ids as $id) {
|
||||
$status = StatusService::get($id, false);
|
||||
if($status && isset($status['account'], $status['account']['id'])) {
|
||||
if($status['account']['id'] == $followingId) {
|
||||
HomeTimelineService::rem($actorId, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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\HomeTimelineService;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
|
||||
class FeedWarmCachePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $pid;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hfp:warm-cache:pid:' . $this->pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hfp:warm-cache:pid:{$this->pid}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($pid)
|
||||
{
|
||||
$this->pid = $pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$pid = $this->pid;
|
||||
HomeTimelineService::warmCache($pid, true, 400, true);
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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\Hashtag;
|
||||
use App\StatusHashtag;
|
||||
use App\UserFilter;
|
||||
use App\Models\UserDomainBlock;
|
||||
use App\Services\HashtagFollowService;
|
||||
use App\Services\HomeTimelineService;
|
||||
use App\Services\StatusService;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
|
||||
class HashtagInsertFanoutPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $hashtag;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hfp:hashtag:fanout:insert:' . $this->hashtag->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hfp:hashtag:fanout:insert:{$this->hashtag->id}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(StatusHashtag $hashtag)
|
||||
{
|
||||
$this->hashtag = $hashtag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$hashtag = $this->hashtag;
|
||||
$sid = $hashtag->status_id;
|
||||
$status = StatusService::get($sid, false);
|
||||
|
||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||
$skipIds = [];
|
||||
|
||||
if(strtolower(config('pixelfed.domain.app')) !== $domain) {
|
||||
$skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray();
|
||||
}
|
||||
|
||||
$filters = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
||||
|
||||
if($filters && count($filters)) {
|
||||
$skipIds = array_merge($skipIds, $filters);
|
||||
}
|
||||
|
||||
$skipIds = array_unique(array_values($skipIds));
|
||||
|
||||
$ids = HashtagFollowService::getPidByHid($hashtag->hashtag_id);
|
||||
|
||||
if(!$ids || !count($ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($ids as $id) {
|
||||
if(!in_array($id, $skipIds)) {
|
||||
HomeTimelineService::add($id, $hashtag->status_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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\Hashtag;
|
||||
use App\StatusHashtag;
|
||||
use App\Services\HashtagFollowService;
|
||||
use App\Services\HomeTimelineService;
|
||||
use App\Services\StatusService;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
|
||||
class HashtagRemoveFanoutPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $sid;
|
||||
protected $hid;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'hfp:hashtag:fanout:remove:' . $this->hid . ':' . $this->sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("hfp:hashtag:fanout:remove:{$this->hid}:{$this->sid}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($sid, $hid)
|
||||
{
|
||||
$this->sid = $sid;
|
||||
$this->hid = $hid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$sid = $this->sid;
|
||||
$hid = $this->hid;
|
||||
$status = StatusService::get($sid, false);
|
||||
|
||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ids = HashtagFollowService::getPidByHid($hid);
|
||||
|
||||
if(!$ids || !count($ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($ids as $id) {
|
||||
HomeTimelineService::rem($id, $sid);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\HomeFeedPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Follower;
|
||||
use App\Hashtag;
|
||||
use App\StatusHashtag;
|
||||
use App\Services\HashtagFollowService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\HomeTimelineService;
|
||||
|
||||
class HashtagUnfollowPipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $pid;
|
||||
protected $hid;
|
||||
protected $slug;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($hid, $pid, $slug)
|
||||
{
|
||||
$this->hid = $hid;
|
||||
$this->pid = $pid;
|
||||
$this->slug = $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$hid = $this->hid;
|
||||
$pid = $this->pid;
|
||||
$slug = strtolower($this->slug);
|
||||
|
||||
$statusIds = HomeTimelineService::get($pid, 0, -1);
|
||||
|
||||
$followingIds = Cache::remember('profile:following:'.$pid, 1209600, function() use($pid) {
|
||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||
return $following->push($pid)->toArray();
|
||||
});
|
||||
|
||||
foreach($statusIds as $id) {
|
||||
$status = StatusService::get($id, false);
|
||||
if(!$status || empty($status['tags'])) {
|
||||
HomeTimelineService::rem($pid, $id);
|
||||
continue;
|
||||
}
|
||||
$following = in_array((int) $status['account']['id'], $followingIds);
|
||||
if($following === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tags = collect($status['tags'])->map(function($tag) {
|
||||
return strtolower($tag['name']);
|
||||
})->filter()->values()->toArray();
|
||||
|
||||
if(in_array($slug, $tags)) {
|
||||
HomeTimelineService::rem($pid, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\InternalPipeline;
|
||||
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Notification;
|
||||
use Cache;
|
||||
use App\Services\NotificationService;
|
||||
|
||||
class NotificationEpochUpdatePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 1500;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'ip:notification-epoch-update';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping('ip:notification-epoch-update'))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$rec = Notification::where('created_at', '>', now()->subMonths(6))->first();
|
||||
$id = 1;
|
||||
if($rec) {
|
||||
$id = $rec->id;
|
||||
}
|
||||
Cache::put(NotificationService::EPOCH_CACHE_KEY . '6', $id, 1209600);
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\ProfilePipeline;
|
||||
|
||||
use Illuminate\Bus\Batchable;
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Follower;
|
||||
use App\Profile;
|
||||
use App\Notification;
|
||||
use DB;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\NotificationService;
|
||||
|
||||
class ProfilePurgeFollowersByDomain implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $pid;
|
||||
protected $domain;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'followers:v1:purge-by-domain:' . $this->pid . ':d-' . $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("followers:v1:purge-by-domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($pid, $domain)
|
||||
{
|
||||
$this->pid = $pid;
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->batch()->cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pid = $this->pid;
|
||||
$domain = $this->domain;
|
||||
|
||||
$query = 'SELECT f.*
|
||||
FROM followers f
|
||||
JOIN profiles p ON p.id = f.profile_id OR p.id = f.following_id
|
||||
WHERE (f.profile_id = ? OR f.following_id = ?)
|
||||
AND p.domain = ?;';
|
||||
$params = [$pid, $pid, $domain];
|
||||
|
||||
foreach(DB::cursor($query, $params) as $n) {
|
||||
if(!$n || !$n->id) {
|
||||
continue;
|
||||
}
|
||||
$follower = Follower::find($n->id);
|
||||
if($follower->following_id == $pid && $follower->profile_id) {
|
||||
FollowerService::remove($follower->profile_id, $pid, true);
|
||||
$follower->delete();
|
||||
} else if ($follower->profile_id == $pid && $follower->following_id) {
|
||||
FollowerService::remove($follower->following_id, $pid, true);
|
||||
$follower->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$profile = Profile::find($pid);
|
||||
|
||||
$followerCount = DB::table('profiles')
|
||||
->join('followers', 'profiles.id', '=', 'followers.following_id')
|
||||
->where('followers.following_id', $pid)
|
||||
->count();
|
||||
|
||||
$followingCount = DB::table('profiles')
|
||||
->join('followers', 'profiles.id', '=', 'followers.following_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
->count();
|
||||
|
||||
$profile->followers_count = $followerCount;
|
||||
$profile->following_count = $followingCount;
|
||||
$profile->save();
|
||||
|
||||
AccountService::del($profile->id);
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\ProfilePipeline;
|
||||
|
||||
use Illuminate\Bus\Batchable;
|
||||
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use App\Notification;
|
||||
use DB;
|
||||
use App\Services\NotificationService;
|
||||
|
||||
class ProfilePurgeNotificationsByDomain implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $pid;
|
||||
protected $domain;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'notify:v1:purge-by-domain:' . $this->pid . ':d-' . $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("notify:v1:purge-by-domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($pid, $domain)
|
||||
{
|
||||
$this->pid = $pid;
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->batch()->cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pid = $this->pid;
|
||||
$domain = $this->domain;
|
||||
|
||||
$query = 'SELECT notifications.*
|
||||
FROM profiles
|
||||
JOIN notifications on profiles.id = notifications.actor_id
|
||||
WHERE notifications.profile_id = ?
|
||||
AND profiles.domain = ?';
|
||||
$params = [$pid, $domain];
|
||||
|
||||
foreach(DB::cursor($query, $params) as $n) {
|
||||
if(!$n || !$n->id) {
|
||||
continue;
|
||||
}
|
||||
Notification::where('id', $n->id)->delete();
|
||||
NotificationService::del($pid, $n->id);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\VideoPipeline;
|
||||
|
||||
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 FFMpeg\Format\Video\X264;
|
||||
use FFMpeg;
|
||||
use Cache;
|
||||
use App\Services\MediaService;
|
||||
use App\Services\StatusService;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
|
||||
class VideoHlsPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $media;
|
||||
|
||||
public $timeout = 900;
|
||||
public $tries = 3;
|
||||
public $maxExceptions = 1;
|
||||
public $failOnTimeout = true;
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $uniqueFor = 3600;
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'media:video-hls:id-' . $this->media->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping("media:video-hls:id-{$this->media->id}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($media)
|
||||
{
|
||||
$this->media = $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$depCheck = Cache::rememberForever('video-pipeline:hls:depcheck', function() {
|
||||
$bin = config('laravel-ffmpeg.ffmpeg.binaries');
|
||||
$output = shell_exec($bin . ' -version');
|
||||
if($output && preg_match('/ffmpeg version ([^\s]+)/', $output, $matches)) {
|
||||
$version = $matches[1];
|
||||
return (version_compare($version, config('laravel-ffmpeg.min_hls_version')) >= 0) ? 'ok' : false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if(!$depCheck || $depCheck !== 'ok') {
|
||||
return;
|
||||
}
|
||||
|
||||
$media = $this->media;
|
||||
|
||||
$bitrate = (new X264)->setKiloBitrate(config('media.hls.bitrate') ?? 1000);
|
||||
|
||||
$mp4 = $media->media_path;
|
||||
$man = str_replace('.mp4', '.m3u8', $mp4);
|
||||
|
||||
FFMpeg::fromDisk('local')
|
||||
->open($mp4)
|
||||
->exportForHLS()
|
||||
->setSegmentLength(16)
|
||||
->setKeyFrameInterval(48)
|
||||
->addFormat($bitrate)
|
||||
->save($man);
|
||||
|
||||
$media->hls_path = $man;
|
||||
$media->hls_transcoded_at = now();
|
||||
$media->save();
|
||||
|
||||
MediaService::del($media->status_id);
|
||||
usleep(50000);
|
||||
StatusService::del($media->status_id);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Services\AccountService;
|
||||
use App\Profile;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'item_id');
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DefaultDomainBlock extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class HashtagRelated extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates and other custom formats.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'related_tags' => 'array',
|
||||
'last_calculated_at' => 'datetime',
|
||||
'last_moderated_at' => 'datetime',
|
||||
];
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Profile;
|
||||
|
||||
class UserDomainBlock extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'profile_id');
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\HashtagFollow;
|
||||
use App\Services\HashtagFollowService;
|
||||
use App\Jobs\HomeFeedPipeline\HashtagUnfollowPipeline;
|
||||
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
|
||||
|
||||
class HashtagFollowObserver implements ShouldHandleEventsAfterCommit
|
||||
{
|
||||
/**
|
||||
* Handle the HashtagFollow "created" event.
|
||||
*/
|
||||
public function created(HashtagFollow $hashtagFollow): void
|
||||
{
|
||||
HashtagFollowService::add($hashtagFollow->hashtag_id, $hashtagFollow->profile_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the HashtagFollow "updated" event.
|
||||
*/
|
||||
public function updated(HashtagFollow $hashtagFollow): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the HashtagFollow "deleting" event.
|
||||
*/
|
||||
public function deleting(HashtagFollow $hashtagFollow): void
|
||||
{
|
||||
HashtagFollowService::unfollow($hashtagFollow->hashtag_id, $hashtagFollow->profile_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the HashtagFollow "restored" event.
|
||||
*/
|
||||
public function restored(HashtagFollow $hashtagFollow): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the HashtagFollow "force deleted" event.
|
||||
*/
|
||||
public function forceDeleted(HashtagFollow $hashtagFollow): void
|
||||
{
|
||||
HashtagFollowService::unfollow($hashtagFollow->hashtag_id, $hashtagFollow->profile_id);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use App\Hashtag;
|
||||
use App\StatusHashtag;
|
||||
use App\HashtagFollow;
|
||||
|
||||
class HashtagFollowService
|
||||
{
|
||||
const FOLLOW_KEY = 'pf:services:hashtag-follows:v1:';
|
||||
const CACHE_KEY = 'pf:services:hfs:byHid:';
|
||||
const CACHE_WARMED = 'pf:services:hfs:wc:byHid';
|
||||
|
||||
public static function getPidByHid($hid)
|
||||
{
|
||||
if(!self::isWarm($hid)) {
|
||||
return self::warmCache($hid);
|
||||
}
|
||||
return self::get($hid);
|
||||
}
|
||||
|
||||
public static function unfollow($hid, $pid)
|
||||
{
|
||||
return Redis::zrem(self::CACHE_KEY . $hid, $pid);
|
||||
}
|
||||
|
||||
public static function add($hid, $pid)
|
||||
{
|
||||
return Redis::zadd(self::CACHE_KEY . $hid, $pid, $pid);
|
||||
}
|
||||
|
||||
public static function rem($hid, $pid)
|
||||
{
|
||||
return Redis::zrem(self::CACHE_KEY . $hid, $pid);
|
||||
}
|
||||
|
||||
public static function get($hid)
|
||||
{
|
||||
return Redis::zrange(self::CACHE_KEY . $hid, 0, -1);
|
||||
}
|
||||
|
||||
public static function count($hid)
|
||||
{
|
||||
return Redis::zcard(self::CACHE_KEY . $hid);
|
||||
}
|
||||
|
||||
public static function warmCache($hid)
|
||||
{
|
||||
foreach(HashtagFollow::whereHashtagId($hid)->lazyById(20, 'id') as $h) {
|
||||
if($h) {
|
||||
self::add($h->hashtag_id, $h->profile_id);
|
||||
}
|
||||
}
|
||||
|
||||
self::setWarm($hid);
|
||||
|
||||
return self::get($hid);
|
||||
}
|
||||
|
||||
public static function isWarm($hid)
|
||||
{
|
||||
return Redis::zcount(self::CACHE_KEY . $hid, 0, -1) ?? Redis::zscore(self::CACHE_WARMED, $hid) != null;
|
||||
}
|
||||
|
||||
public static function setWarm($hid)
|
||||
{
|
||||
return Redis::zadd(self::CACHE_WARMED, $hid, $hid);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use DB;
|
||||
use App\StatusHashtag;
|
||||
use App\Models\HashtagRelated;
|
||||
|
||||
class HashtagRelatedService
|
||||
{
|
||||
public static function get($id)
|
||||
{
|
||||
$tag = HashtagRelated::whereHashtagId($id)->first();
|
||||
if(!$tag) {
|
||||
return [];
|
||||
}
|
||||
return $tag->related_tags;
|
||||
}
|
||||
|
||||
public static function fetchRelatedTags($tag)
|
||||
{
|
||||
$res = StatusHashtag::query()
|
||||
->select('h2.name', DB::raw('COUNT(*) as related_count'))
|
||||
->join('status_hashtags as hs2', function ($join) {
|
||||
$join->on('status_hashtags.status_id', '=', 'hs2.status_id')
|
||||
->whereRaw('status_hashtags.hashtag_id != hs2.hashtag_id');
|
||||
})
|
||||
->join('hashtags as h1', 'status_hashtags.hashtag_id', '=', 'h1.id')
|
||||
->join('hashtags as h2', 'hs2.hashtag_id', '=', 'h2.id')
|
||||
->where('h1.name', '=', $tag)
|
||||
->groupBy('h2.name')
|
||||
->orderBy('related_count', 'desc')
|
||||
->limit(30)
|
||||
->get();
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use App\Follower;
|
||||
use App\Status;
|
||||
use App\Models\UserDomainBlock;
|
||||
|
||||
class HomeTimelineService
|
||||
{
|
||||
const CACHE_KEY = 'pf:services:timeline:home:';
|
||||
const FOLLOWER_FEED_POST_LIMIT = 10;
|
||||
|
||||
public static function get($id, $start = 0, $stop = 10)
|
||||
{
|
||||
if($stop > 100) {
|
||||
$stop = 100;
|
||||
}
|
||||
|
||||
return Redis::zrevrange(self::CACHE_KEY . $id, $start, $stop);
|
||||
}
|
||||
|
||||
public static function getRankedMaxId($id, $start = null, $limit = 10)
|
||||
{
|
||||
if(!$start) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY . $id, $start, '-inf', [
|
||||
'withscores' => true,
|
||||
'limit' => [1, $limit - 1]
|
||||
]));
|
||||
}
|
||||
|
||||
public static function getRankedMinId($id, $end = null, $limit = 10)
|
||||
{
|
||||
if(!$end) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY . $id, '+inf', $end, [
|
||||
'withscores' => true,
|
||||
'limit' => [0, $limit]
|
||||
]));
|
||||
}
|
||||
|
||||
public static function add($id, $val)
|
||||
{
|
||||
if(self::count($id) >= 400) {
|
||||
Redis::zpopmin(self::CACHE_KEY . $id);
|
||||
}
|
||||
|
||||
return Redis::zadd(self::CACHE_KEY .$id, $val, $val);
|
||||
}
|
||||
|
||||
public static function rem($id, $val)
|
||||
{
|
||||
return Redis::zrem(self::CACHE_KEY . $id, $val);
|
||||
}
|
||||
|
||||
public static function count($id)
|
||||
{
|
||||
return Redis::zcard(self::CACHE_KEY . $id);
|
||||
}
|
||||
|
||||
public static function warmCache($id, $force = false, $limit = 100, $returnIds = false)
|
||||
{
|
||||
if(self::count($id) == 0 || $force == true) {
|
||||
Redis::del(self::CACHE_KEY . $id);
|
||||
$following = Cache::remember('profile:following:'.$id, 1209600, function() use($id) {
|
||||
$following = Follower::whereProfileId($id)->pluck('following_id');
|
||||
return $following->push($id)->toArray();
|
||||
});
|
||||
|
||||
$minId = SnowflakeService::byDate(now()->subMonths(6));
|
||||
|
||||
$filters = UserFilterService::filters($id);
|
||||
|
||||
if($filters && count($filters)) {
|
||||
$following = array_diff($following, $filters);
|
||||
}
|
||||
|
||||
$domainBlocks = UserDomainBlock::whereProfileId($id)->pluck('domain')->toArray();
|
||||
|
||||
$ids = Status::where('id', '>', $minId)
|
||||
->whereIn('profile_id', $following)
|
||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||
->whereIn('visibility',['public', 'unlisted', 'private'])
|
||||
->orderByDesc('id')
|
||||
->limit($limit)
|
||||
->pluck('id');
|
||||
|
||||
foreach($ids as $pid) {
|
||||
$status = StatusService::get($pid, false);
|
||||
if(!$status || !isset($status['account'], $status['url'])) {
|
||||
continue;
|
||||
}
|
||||
if($domainBlocks && count($domainBlocks)) {
|
||||
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||
if(in_array($domain, $domainBlocks)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
self::add($id, $pid);
|
||||
}
|
||||
|
||||
return $returnIds ? $ids : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Media;
|
||||
|
||||
use Storage;
|
||||
|
||||
class MediaHlsService
|
||||
{
|
||||
public static function allFiles($media)
|
||||
{
|
||||
$path = $media->media_path;
|
||||
if(!$path) { return; }
|
||||
$parts = explode('/', $path);
|
||||
$filename = array_pop($parts);
|
||||
$dir = implode('/', $parts);
|
||||
[$name, $ext] = explode('.', $filename);
|
||||
|
||||
$files = Storage::files($dir);
|
||||
|
||||
return collect($files)
|
||||
->filter(function($p) use($dir, $name) {
|
||||
return str_starts_with($p, $dir . '/' . $name);
|
||||
})
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue