mirror of https://github.com/pixelfed/pixelfed
Merge branch 'staging' into dev
commit
c2ce63ecd3
@ -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();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -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,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,97 @@
|
|||||||
|
<?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\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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skipIds = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
||||||
|
|
||||||
|
foreach($ids as $id) {
|
||||||
|
if(!in_array($id, $skipIds)) {
|
||||||
|
HomeTimelineService::add($id, $this->sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
<?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\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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skipIds = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
||||||
|
|
||||||
|
foreach($ids as $id) {
|
||||||
|
if(!in_array($id, $skipIds)) {
|
||||||
|
HomeTimelineService::add($id, $this->sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,102 @@
|
|||||||
|
<?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\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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skipIds = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
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,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,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,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,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,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Status;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the keys for authentication (VAPID).
|
||||||
|
* These keys must be safely stored and should not change.
|
||||||
|
*/
|
||||||
|
'vapid' => [
|
||||||
|
'subject' => env('VAPID_SUBJECT'),
|
||||||
|
'public_key' => env('VAPID_PUBLIC_KEY'),
|
||||||
|
'private_key' => env('VAPID_PRIVATE_KEY'),
|
||||||
|
'pem_file' => env('VAPID_PEM_FILE'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is model that will be used to for push subscriptions.
|
||||||
|
*/
|
||||||
|
'model' => \NotificationChannels\WebPush\PushSubscription::class,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the name of the table that will be created by the migration and
|
||||||
|
* used by the PushSubscription model shipped with this package.
|
||||||
|
*/
|
||||||
|
'table_name' => env('WEBPUSH_DB_TABLE', 'push_subscriptions'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the database connection that will be used by the migration and
|
||||||
|
* the PushSubscription model shipped with this package.
|
||||||
|
*/
|
||||||
|
'database_connection' => env('WEBPUSH_DB_CONNECTION', env('DB_CONNECTION', 'mysql')),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Guzzle client options used by Minishlink\WebPush.
|
||||||
|
*/
|
||||||
|
'client_options' => [],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Google Cloud Messaging.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
'gcm' => [
|
||||||
|
'key' => env('GCM_KEY'),
|
||||||
|
'sender_id' => env('GCM_SENDER_ID'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->index('followers_count', 'profiles_followers_count_index');
|
||||||
|
$table->index('following_count', 'profiles_following_count_index');
|
||||||
|
$table->index('status_count', 'profiles_status_count_index');
|
||||||
|
$table->index('is_private', 'profiles_is_private_index');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->dropIndex('profiles_followers_count_index');
|
||||||
|
$table->dropIndex('profiles_following_count_index');
|
||||||
|
$table->dropIndex('profiles_status_count_index');
|
||||||
|
$table->dropIndex('profiles_is_private_index');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('hashtag_related', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('hashtag_id')->unsigned()->unique()->index();
|
||||||
|
$table->json('related_tags')->nullable();
|
||||||
|
$table->bigInteger('agg_score')->unsigned()->nullable()->index();
|
||||||
|
$table->timestamp('last_calculated_at')->nullable()->index();
|
||||||
|
$table->timestamp('last_moderated_at')->nullable()->index();
|
||||||
|
$table->boolean('skip_refresh')->default(false)->index();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('hashtag_related');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('places', function (Blueprint $table) {
|
||||||
|
$table->string('state')->nullable()->index()->after('name');
|
||||||
|
$table->tinyInteger('score')->default(0)->index()->after('long');
|
||||||
|
$table->unsignedBigInteger('cached_post_count')->nullable();
|
||||||
|
$table->timestamp('last_checked_at')->nullable()->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('places', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('state');
|
||||||
|
$table->dropColumn('score');
|
||||||
|
$table->dropColumn('cached_post_count');
|
||||||
|
$table->dropColumn('last_checked_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreatePushSubscriptionsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::connection(config('webpush.database_connection'))->create(config('webpush.table_name'), function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->morphs('subscribable');
|
||||||
|
$table->string('endpoint', 500)->unique();
|
||||||
|
$table->string('public_key')->nullable();
|
||||||
|
$table->string('auth_token')->nullable();
|
||||||
|
$table->string('content_encoding')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::connection(config('webpush.database_connection'))->dropIfExists(config('webpush.table_name'));
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue