瀏覽代碼

Merge branch 'staging' of github.com:pixelfed/pixelfed into jippi-fork

Christian Winther 1 年之前
父節點
當前提交
091de696c2

+ 5 - 0
CHANGELOG.md

@@ -5,6 +5,11 @@
 ### Updates
 
 - Update SoftwareUpdateService, add command to refresh latest versions ([632f2cb6](https://github.com/pixelfed/pixelfed/commit/632f2cb6))
+- Update Post.vue, fix cache bug ([3a27e637](https://github.com/pixelfed/pixelfed/commit/3a27e637))
+- Update StatusHashtagService, use more efficient cached count ([592c8412](https://github.com/pixelfed/pixelfed/commit/592c8412))
+- Update DiscoverController, handle discover hashtag redirects ([18382e8a](https://github.com/pixelfed/pixelfed/commit/18382e8a))
+- Update ApiV1Controller, use admin filter service ([94503a1c](https://github.com/pixelfed/pixelfed/commit/94503a1c))
+- Update SearchApiV2Service, use more efficient query ([cee618e8](https://github.com/pixelfed/pixelfed/commit/cee618e8))
 -  ([](https://github.com/pixelfed/pixelfed/commit/))
 
 ## [v0.11.13 (2024-03-05)](https://github.com/pixelfed/pixelfed/compare/v0.11.12...v0.11.13)

+ 17 - 1
app/Http/Controllers/Api/ApiV1Controller.php

@@ -37,6 +37,7 @@ use App\Models\Conversation;
 use App\Notification;
 use App\Profile;
 use App\Services\AccountService;
+use App\Services\AdminShadowFilterService;
 use App\Services\BookmarkService;
 use App\Services\BouncerService;
 use App\Services\CollectionService;
@@ -2648,7 +2649,7 @@ class ApiV1Controller extends Controller
         $domainBlocks = UserFilterService::domainBlocks($user->profile_id);
         $hideNsfw = config('instance.hide_nsfw_on_public_feeds');
         $amin = SnowflakeService::byDate(now()->subDays(config('federation.network_timeline_days_falloff')));
-
+        $asf = AdminShadowFilterService::getHideFromPublicFeedsList();
         if ($local && $remote) {
             $feed = Status::select(
                 'id',
@@ -2824,6 +2825,21 @@ class ApiV1Controller extends Controller
 
                 return ! in_array($domain, $domainBlocks);
             })
+            ->filter(function ($s) use ($asf, $user) {
+                if (! $asf || count($asf) === 0) {
+                    return true;
+                }
+
+                if (in_array($s['account']['id'], $asf)) {
+                    if ($user->profile_id == $s['account']['id']) {
+                        return true;
+                    }
+
+                    return false;
+                }
+
+                return true;
+            })
             ->take($limit)
             ->values();
 

+ 363 - 350
app/Http/Controllers/DiscoverController.php

@@ -2,366 +2,379 @@
 
 namespace App\Http\Controllers;
 
-use App\{
-	DiscoverCategory,
-	Follower,
-	Hashtag,
-	HashtagFollow,
-	Instance,
-	Like,
-	Profile,
-	Status,
-	StatusHashtag,
-	UserFilter
-};
-use Auth, DB, Cache;
-use Illuminate\Http\Request;
+use App\Hashtag;
+use App\Instance;
+use App\Like;
 use App\Services\BookmarkService;
 use App\Services\ConfigCacheService;
 use App\Services\HashtagService;
 use App\Services\LikeService;
 use App\Services\ReblogService;
-use App\Services\StatusHashtagService;
 use App\Services\SnowflakeService;
+use App\Services\StatusHashtagService;
 use App\Services\StatusService;
 use App\Services\TrendingHashtagService;
 use App\Services\UserFilterService;
+use App\Status;
+use Auth;
+use Cache;
+use DB;
+use Illuminate\Http\Request;
 
 class DiscoverController extends Controller
 {
-	public function home(Request $request)
-	{
-		abort_if(!Auth::check() && config('instance.discover.public') == false, 403);
-		return view('discover.home');
-	}
-
-	public function showTags(Request $request, $hashtag)
-	{
-			abort_if(!config('instance.discover.tags.is_public') && !Auth::check(), 403);
-
-			$tag = Hashtag::whereName($hashtag)
-				->orWhere('slug', $hashtag)
-				->where('is_banned', '!=', true)
-				->firstOrFail();
-			$tagCount = StatusHashtagService::count($tag->id);
-			return view('discover.tags.show', compact('tag', 'tagCount'));
-	}
-
-	public function getHashtags(Request $request)
-	{
-		$user = $request->user();
-		abort_if(!config('instance.discover.tags.is_public') && !$user, 403);
-
-		$this->validate($request, [
-			'hashtag' => 'required|string|min:1|max:124',
-			'page' => 'nullable|integer|min:1|max:' . ($user ? 29 : 3)
-		]);
-
-		$page = $request->input('page') ?? '1';
-		$end = $page > 1 ? $page * 9 : 0;
-		$tag = $request->input('hashtag');
-
-		if(config('database.default') === 'pgsql') {
-			$hashtag = Hashtag::where('name', 'ilike', $tag)->firstOrFail();
-		} else {
-			$hashtag = Hashtag::whereName($tag)->firstOrFail();
-		}
-
-		if($hashtag->is_banned == true) {
-			return [];
-		}
-		if($user) {
-			$res['follows'] = HashtagService::isFollowing($user->profile_id, $hashtag->id);
-		}
-		$res['hashtag'] = [
-			'name' => $hashtag->name,
-			'url' => $hashtag->url()
-		];
-		if($user) {
-			$tags = StatusHashtagService::get($hashtag->id, $page, $end);
-			$res['tags'] = collect($tags)
-				->map(function($tag) use($user) {
-					$tag['status']['favourited'] = (bool) LikeService::liked($user->profile_id, $tag['status']['id']);
-					$tag['status']['reblogged'] = (bool) ReblogService::get($user->profile_id, $tag['status']['id']);
-					$tag['status']['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $tag['status']['id']);
-					return $tag;
-				})
-				->filter(function($tag) {
-					if(!StatusService::get($tag['status']['id'])) {
-						return false;
-					}
-					return true;
-				})
-				->values();
-		} else {
-			if($page != 1) {
-				$res['tags'] = [];
-				return $res;
-			}
-			$key = 'discover:tags:public_feed:' . $hashtag->id . ':page:' . $page;
-			$tags = Cache::remember($key, 43200, function() use($hashtag, $page, $end) {
-				return collect(StatusHashtagService::get($hashtag->id, $page, $end))
-					->filter(function($tag) {
-						if(!$tag['status']['local']) {
-							return false;
-						}
-						return true;
-					})
-					->values();
-			});
-			$res['tags'] = collect($tags)
-				->filter(function($tag) {
-					if(!StatusService::get($tag['status']['id'])) {
-						return false;
-					}
-					return true;
-				})
-				->values();
-		}
-		return $res;
-	}
-
-	public function profilesDirectory(Request $request)
-	{
-		return redirect('/')->with('statusRedirect', 'The Profile Directory is unavailable at this time.');
-	}
-
-	public function profilesDirectoryApi(Request $request)
-	{
-		return ['error' => 'Temporarily unavailable.'];
-	}
-
-	public function trendingApi(Request $request)
-	{
-		abort_if(config('instance.discover.public') == false && !$request->user(), 403);
-
-		$this->validate($request, [
-			'range' => 'nullable|string|in:daily,monthly,yearly',
-		]);
-
-		$range = $request->input('range');
-		$days = $range == 'monthly' ? 31 : ($range == 'daily' ? 1 : 365);
-		$ttls = [
-			1 => 1500,
-			31 => 14400,
-			365 => 86400
-		];
-		$key = ':api:discover:trending:v2.12:range:' . $days;
-
-		$ids = Cache::remember($key, $ttls[$days], function() use($days) {
-			$min_id = SnowflakeService::byDate(now()->subDays($days));
-			return DB::table('statuses')
-				->select(
-					'id',
-					'scope',
-					'type',
-					'is_nsfw',
-					'likes_count',
-					'created_at'
-				)
-				->where('id', '>', $min_id)
-				->whereNull('uri')
-				->whereScope('public')
-				->whereIn('type', [
-					'photo',
-					'photo:album',
-					'video'
-				])
-				->whereIsNsfw(false)
-				->orderBy('likes_count','desc')
-				->take(30)
-				->pluck('id');
-		});
-
-		$filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
-
-		$res = $ids->map(function($s) {
-			return StatusService::get($s);
-		})->filter(function($s) use($filtered) {
-			return
-				$s &&
-				!in_array($s['account']['id'], $filtered) &&
-				isset($s['account']);
-		})->values();
-
-		return response()->json($res);
-	}
-
-	public function trendingHashtags(Request $request)
-	{
-		abort_if(!$request->user(), 403);
-
-		$res = TrendingHashtagService::getTrending();
-		return $res;
-	}
-
-	public function trendingPlaces(Request $request)
-	{
-		return [];
-	}
-
-	public function myMemories(Request $request)
-	{
-		abort_if(!$request->user(), 404);
-		$pid = $request->user()->profile_id;
-		abort_if(!$this->config()['memories']['enabled'], 404);
-		$type = $request->input('type') ?? 'posts';
-
-		switch($type) {
-			case 'posts':
-				$res = Status::whereProfileId($pid)
-					->whereDay('created_at', date('d'))
-					->whereMonth('created_at', date('m'))
-					->whereYear('created_at', '!=', date('Y'))
-					->whereNull(['reblog_of_id', 'in_reply_to_id'])
-					->limit(20)
-					->pluck('id')
-					->map(function($id) {
-						return StatusService::get($id, false);
-					})
-					->filter(function($post) {
-						return $post && isset($post['account']);
-					})
-					->values();
-			break;
-
-			case 'liked':
-				$res = Like::whereProfileId($pid)
-					->whereDay('created_at', date('d'))
-					->whereMonth('created_at', date('m'))
-					->whereYear('created_at', '!=', date('Y'))
-					->orderByDesc('status_id')
-					->limit(20)
-					->pluck('status_id')
-					->map(function($id) {
-						$status = StatusService::get($id, false);
-						$status['favourited'] = true;
-						return $status;
-					})
-					->filter(function($post) {
-						return $post && isset($post['account']);
-					})
-					->values();
-			break;
-		}
-
-		return $res;
-	}
-
-	public function accountInsightsPopularPosts(Request $request)
-	{
-		abort_if(!$request->user(), 404);
-		$pid = $request->user()->profile_id;
-		abort_if(!$this->config()['insights']['enabled'], 404);
-		$posts = Cache::remember('pf:discover:metro2:accinsights:popular:' . $pid, 43200, function() use ($pid) {
-			return Status::whereProfileId($pid)
-			->whereNotNull('likes_count')
-			->orderByDesc('likes_count')
-			->limit(12)
-			->pluck('id')
-			->map(function($id) {
-				return StatusService::get($id, false);
-			})
-			->filter(function($post) {
-				return $post && isset($post['account']);
-			})
-			->values();
-		});
-
-		return $posts;
-	}
-
-	public function config()
-	{
-		$cc = ConfigCacheService::get('config.discover.features');
-		if($cc) {
-			return is_string($cc) ? json_decode($cc, true) : $cc;
-		}
-		return [
-			'hashtags' => [
-				'enabled' => false,
-			],
-			'memories' => [
-				'enabled' => false,
-			],
-			'insights' => [
-				'enabled' => false,
-			],
-			'friends' => [
-				'enabled' => false,
-			],
-			'server' => [
-				'enabled' => false,
-				'mode' => 'allowlist',
-				'domains' => []
-			]
-		];
-	}
-
-	public function serverTimeline(Request $request)
-	{
-		abort_if(!$request->user(), 404);
-		abort_if(!$this->config()['server']['enabled'], 404);
-		$pid = $request->user()->profile_id;
-		$domain = $request->input('domain');
-		$config = $this->config();
-		$domains = explode(',', $config['server']['domains']);
-		abort_unless(in_array($domain, $domains), 400);
-
-		$res = Status::whereNotNull('uri')
-			->where('uri', 'like', 'https://' . $domain . '%')
-			->whereNull(['in_reply_to_id', 'reblog_of_id'])
-			->orderByDesc('id')
-			->limit(12)
-			->pluck('id')
-			->map(function($id) {
-				return StatusService::get($id);
-			})
-			->filter(function($post) {
-				return $post && isset($post['account']);
-			})
-			->values();
-		return $res;
-	}
-
-	public function enabledFeatures(Request $request)
-	{
-		abort_if(!$request->user(), 404);
-		return $this->config();
-	}
-
-	public function updateFeatures(Request $request)
-	{
-		abort_if(!$request->user(), 404);
-		abort_if(!$request->user()->is_admin, 404);
-		$pid = $request->user()->profile_id;
-		$this->validate($request, [
-			'features.friends.enabled' => 'boolean',
-			'features.hashtags.enabled' => 'boolean',
-			'features.insights.enabled' => 'boolean',
-			'features.memories.enabled' => 'boolean',
-			'features.server.enabled' => 'boolean',
-		]);
-		$res = $request->input('features');
-		if($res['server'] && isset($res['server']['domains']) && !empty($res['server']['domains'])) {
-			$parts = explode(',', $res['server']['domains']);
-			$parts = array_filter($parts, function($v) {
-				$len = strlen($v);
-				$pos = strpos($v, '.');
-				$domain = trim($v);
-				if($pos == false || $pos == ($len + 1)) {
-					return false;
-				}
-				if(!Instance::whereDomain($domain)->exists()) {
-					return false;
-				}
-				return true;
-			});
-			$parts = array_slice($parts, 0, 10);
-			$d = implode(',', array_map('trim', $parts));
-			$res['server']['domains'] = $d;
-		}
-		ConfigCacheService::put('config.discover.features', json_encode($res));
-		return $res;
-	}
+    public function home(Request $request)
+    {
+        abort_if(! Auth::check() && config('instance.discover.public') == false, 403);
+
+        return view('discover.home');
+    }
+
+    public function showTags(Request $request, $hashtag)
+    {
+        if ($request->user()) {
+            return redirect('/i/web/hashtag/'.$hashtag.'?src=pd');
+        }
+        abort_if(! config('instance.discover.tags.is_public') && ! Auth::check(), 403);
+
+        $tag = Hashtag::whereName($hashtag)
+            ->orWhere('slug', $hashtag)
+            ->where('is_banned', '!=', true)
+            ->firstOrFail();
+        $tagCount = $tag->cached_count ?? 0;
+
+        return view('discover.tags.show', compact('tag', 'tagCount'));
+    }
+
+    public function getHashtags(Request $request)
+    {
+        $user = $request->user();
+        abort_if(! config('instance.discover.tags.is_public') && ! $user, 403);
+
+        $this->validate($request, [
+            'hashtag' => 'required|string|min:1|max:124',
+            'page' => 'nullable|integer|min:1|max:'.($user ? 29 : 3),
+        ]);
+
+        $page = $request->input('page') ?? '1';
+        $end = $page > 1 ? $page * 9 : 0;
+        $tag = $request->input('hashtag');
+
+        if (config('database.default') === 'pgsql') {
+            $hashtag = Hashtag::where('name', 'ilike', $tag)->firstOrFail();
+        } else {
+            $hashtag = Hashtag::whereName($tag)->firstOrFail();
+        }
+
+        if ($hashtag->is_banned == true) {
+            return [];
+        }
+        if ($user) {
+            $res['follows'] = HashtagService::isFollowing($user->profile_id, $hashtag->id);
+        }
+        $res['hashtag'] = [
+            'name' => $hashtag->name,
+            'url' => $hashtag->url(),
+        ];
+        if ($user) {
+            $tags = StatusHashtagService::get($hashtag->id, $page, $end);
+            $res['tags'] = collect($tags)
+                ->map(function ($tag) use ($user) {
+                    $tag['status']['favourited'] = (bool) LikeService::liked($user->profile_id, $tag['status']['id']);
+                    $tag['status']['reblogged'] = (bool) ReblogService::get($user->profile_id, $tag['status']['id']);
+                    $tag['status']['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $tag['status']['id']);
+
+                    return $tag;
+                })
+                ->filter(function ($tag) {
+                    if (! StatusService::get($tag['status']['id'])) {
+                        return false;
+                    }
+
+                    return true;
+                })
+                ->values();
+        } else {
+            if ($page != 1) {
+                $res['tags'] = [];
+
+                return $res;
+            }
+            $key = 'discover:tags:public_feed:'.$hashtag->id.':page:'.$page;
+            $tags = Cache::remember($key, 43200, function () use ($hashtag, $page, $end) {
+                return collect(StatusHashtagService::get($hashtag->id, $page, $end))
+                    ->filter(function ($tag) {
+                        if (! $tag['status']['local']) {
+                            return false;
+                        }
+
+                        return true;
+                    })
+                    ->values();
+            });
+            $res['tags'] = collect($tags)
+                ->filter(function ($tag) {
+                    if (! StatusService::get($tag['status']['id'])) {
+                        return false;
+                    }
+
+                    return true;
+                })
+                ->values();
+        }
+
+        return $res;
+    }
+
+    public function profilesDirectory(Request $request)
+    {
+        return redirect('/')->with('statusRedirect', 'The Profile Directory is unavailable at this time.');
+    }
+
+    public function profilesDirectoryApi(Request $request)
+    {
+        return ['error' => 'Temporarily unavailable.'];
+    }
+
+    public function trendingApi(Request $request)
+    {
+        abort_if(config('instance.discover.public') == false && ! $request->user(), 403);
+
+        $this->validate($request, [
+            'range' => 'nullable|string|in:daily,monthly,yearly',
+        ]);
+
+        $range = $request->input('range');
+        $days = $range == 'monthly' ? 31 : ($range == 'daily' ? 1 : 365);
+        $ttls = [
+            1 => 1500,
+            31 => 14400,
+            365 => 86400,
+        ];
+        $key = ':api:discover:trending:v2.12:range:'.$days;
+
+        $ids = Cache::remember($key, $ttls[$days], function () use ($days) {
+            $min_id = SnowflakeService::byDate(now()->subDays($days));
+
+            return DB::table('statuses')
+                ->select(
+                    'id',
+                    'scope',
+                    'type',
+                    'is_nsfw',
+                    'likes_count',
+                    'created_at'
+                )
+                ->where('id', '>', $min_id)
+                ->whereNull('uri')
+                ->whereScope('public')
+                ->whereIn('type', [
+                    'photo',
+                    'photo:album',
+                    'video',
+                ])
+                ->whereIsNsfw(false)
+                ->orderBy('likes_count', 'desc')
+                ->take(30)
+                ->pluck('id');
+        });
+
+        $filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
+
+        $res = $ids->map(function ($s) {
+            return StatusService::get($s);
+        })->filter(function ($s) use ($filtered) {
+            return
+                $s &&
+                ! in_array($s['account']['id'], $filtered) &&
+                isset($s['account']);
+        })->values();
+
+        return response()->json($res);
+    }
+
+    public function trendingHashtags(Request $request)
+    {
+        abort_if(! $request->user(), 403);
+
+        $res = TrendingHashtagService::getTrending();
+
+        return $res;
+    }
+
+    public function trendingPlaces(Request $request)
+    {
+        return [];
+    }
+
+    public function myMemories(Request $request)
+    {
+        abort_if(! $request->user(), 404);
+        $pid = $request->user()->profile_id;
+        abort_if(! $this->config()['memories']['enabled'], 404);
+        $type = $request->input('type') ?? 'posts';
+
+        switch ($type) {
+            case 'posts':
+                $res = Status::whereProfileId($pid)
+                    ->whereDay('created_at', date('d'))
+                    ->whereMonth('created_at', date('m'))
+                    ->whereYear('created_at', '!=', date('Y'))
+                    ->whereNull(['reblog_of_id', 'in_reply_to_id'])
+                    ->limit(20)
+                    ->pluck('id')
+                    ->map(function ($id) {
+                        return StatusService::get($id, false);
+                    })
+                    ->filter(function ($post) {
+                        return $post && isset($post['account']);
+                    })
+                    ->values();
+                break;
+
+            case 'liked':
+                $res = Like::whereProfileId($pid)
+                    ->whereDay('created_at', date('d'))
+                    ->whereMonth('created_at', date('m'))
+                    ->whereYear('created_at', '!=', date('Y'))
+                    ->orderByDesc('status_id')
+                    ->limit(20)
+                    ->pluck('status_id')
+                    ->map(function ($id) {
+                        $status = StatusService::get($id, false);
+                        $status['favourited'] = true;
+
+                        return $status;
+                    })
+                    ->filter(function ($post) {
+                        return $post && isset($post['account']);
+                    })
+                    ->values();
+                break;
+        }
+
+        return $res;
+    }
+
+    public function accountInsightsPopularPosts(Request $request)
+    {
+        abort_if(! $request->user(), 404);
+        $pid = $request->user()->profile_id;
+        abort_if(! $this->config()['insights']['enabled'], 404);
+        $posts = Cache::remember('pf:discover:metro2:accinsights:popular:'.$pid, 43200, function () use ($pid) {
+            return Status::whereProfileId($pid)
+                ->whereNotNull('likes_count')
+                ->orderByDesc('likes_count')
+                ->limit(12)
+                ->pluck('id')
+                ->map(function ($id) {
+                    return StatusService::get($id, false);
+                })
+                ->filter(function ($post) {
+                    return $post && isset($post['account']);
+                })
+                ->values();
+        });
+
+        return $posts;
+    }
+
+    public function config()
+    {
+        $cc = ConfigCacheService::get('config.discover.features');
+        if ($cc) {
+            return is_string($cc) ? json_decode($cc, true) : $cc;
+        }
+
+        return [
+            'hashtags' => [
+                'enabled' => false,
+            ],
+            'memories' => [
+                'enabled' => false,
+            ],
+            'insights' => [
+                'enabled' => false,
+            ],
+            'friends' => [
+                'enabled' => false,
+            ],
+            'server' => [
+                'enabled' => false,
+                'mode' => 'allowlist',
+                'domains' => [],
+            ],
+        ];
+    }
+
+    public function serverTimeline(Request $request)
+    {
+        abort_if(! $request->user(), 404);
+        abort_if(! $this->config()['server']['enabled'], 404);
+        $pid = $request->user()->profile_id;
+        $domain = $request->input('domain');
+        $config = $this->config();
+        $domains = explode(',', $config['server']['domains']);
+        abort_unless(in_array($domain, $domains), 400);
+
+        $res = Status::whereNotNull('uri')
+            ->where('uri', 'like', 'https://'.$domain.'%')
+            ->whereNull(['in_reply_to_id', 'reblog_of_id'])
+            ->orderByDesc('id')
+            ->limit(12)
+            ->pluck('id')
+            ->map(function ($id) {
+                return StatusService::get($id);
+            })
+            ->filter(function ($post) {
+                return $post && isset($post['account']);
+            })
+            ->values();
+
+        return $res;
+    }
+
+    public function enabledFeatures(Request $request)
+    {
+        abort_if(! $request->user(), 404);
+
+        return $this->config();
+    }
+
+    public function updateFeatures(Request $request)
+    {
+        abort_if(! $request->user(), 404);
+        abort_if(! $request->user()->is_admin, 404);
+        $pid = $request->user()->profile_id;
+        $this->validate($request, [
+            'features.friends.enabled' => 'boolean',
+            'features.hashtags.enabled' => 'boolean',
+            'features.insights.enabled' => 'boolean',
+            'features.memories.enabled' => 'boolean',
+            'features.server.enabled' => 'boolean',
+        ]);
+        $res = $request->input('features');
+        if ($res['server'] && isset($res['server']['domains']) && ! empty($res['server']['domains'])) {
+            $parts = explode(',', $res['server']['domains']);
+            $parts = array_filter($parts, function ($v) {
+                $len = strlen($v);
+                $pos = strpos($v, '.');
+                $domain = trim($v);
+                if ($pos == false || $pos == ($len + 1)) {
+                    return false;
+                }
+                if (! Instance::whereDomain($domain)->exists()) {
+                    return false;
+                }
+
+                return true;
+            });
+            $parts = array_slice($parts, 0, 10);
+            $d = implode(',', array_map('trim', $parts));
+            $res['server']['domains'] = $d;
+        }
+        ConfigCacheService::put('config.discover.features', json_encode($res));
+
+        return $res;
+    }
 }

+ 83 - 78
app/Services/SearchApiV2Service.php

@@ -2,28 +2,26 @@
 
 namespace App\Services;
 
-use Cache;
-use Illuminate\Support\Facades\Redis;
-use App\{Hashtag, Profile, Status};
+use App\Hashtag;
+use App\Profile;
+use App\Status;
 use App\Transformer\Api\AccountTransformer;
-use App\Transformer\Api\StatusTransformer;
-use League\Fractal;
-use League\Fractal\Serializer\ArraySerializer;
-use League\Fractal\Pagination\IlluminatePaginatorAdapter;
 use App\Util\ActivityPub\Helpers;
 use Illuminate\Support\Str;
-use App\Services\AccountService;
-use App\Services\HashtagService;
-use App\Services\StatusService;
+use League\Fractal;
+use League\Fractal\Serializer\ArraySerializer;
+
 
 class SearchApiV2Service
 {
     private $query;
-    static $mastodonMode = false;
+
+    public static $mastodonMode = false;
 
     public static function query($query, $mastodonMode = false)
     {
         self::$mastodonMode = $mastodonMode;
+
         return (new self)->run($query);
     }
 
@@ -32,51 +30,51 @@ class SearchApiV2Service
         $this->query = $query;
         $q = urldecode($query->input('q'));
 
-        if($query->has('resolve') &&
-            ( Str::startsWith($q, 'https://') ||
+        if ($query->has('resolve') &&
+            (Str::startsWith($q, 'https://') ||
               Str::substrCount($q, '@') >= 1)
         ) {
             return $this->resolveQuery();
         }
 
-        if($query->has('type')) {
+        if ($query->has('type')) {
             switch ($query->input('type')) {
                 case 'accounts':
                     return [
                         'accounts' => $this->accounts(),
                         'hashtags' => [],
-                        'statuses' => []
+                        'statuses' => [],
                     ];
                     break;
                 case 'hashtags':
                     return [
                         'accounts' => [],
                         'hashtags' => $this->hashtags(),
-                        'statuses' => []
+                        'statuses' => [],
                     ];
                     break;
                 case 'statuses':
                     return [
                         'accounts' => [],
                         'hashtags' => [],
-                        'statuses' => $this->statuses()
+                        'statuses' => $this->statuses(),
                     ];
                     break;
             }
         }
 
-        if($query->has('account_id')) {
+        if ($query->has('account_id')) {
             return [
                 'accounts' => [],
                 'hashtags' => [],
-                'statuses' => $this->statusesById()
+                'statuses' => $this->statusesById(),
             ];
         }
 
         return [
             'accounts' => $this->accounts(),
             'hashtags' => $this->hashtags(),
-            'statuses' => $this->statuses()
+            'statuses' => $this->statuses(),
         ];
     }
 
@@ -87,17 +85,17 @@ class SearchApiV2Service
         $limit = $this->query->input('limit') ?? 20;
         $offset = $this->query->input('offset') ?? 0;
         $rawQuery = $initalQuery ? $initalQuery : $this->query->input('q');
-        $query = $rawQuery . '%';
+        $query = $rawQuery.'%';
         $webfingerQuery = $query;
-        if(Str::substrCount($rawQuery, '@') == 1 && substr($rawQuery, 0, 1) !== '@') {
-            $query = '@' . $query;
+        if (Str::substrCount($rawQuery, '@') == 1 && substr($rawQuery, 0, 1) !== '@') {
+            $query = '@'.$query;
         }
-        if(substr($webfingerQuery, 0, 1) !== '@') {
-            $webfingerQuery = '@' . $webfingerQuery;
+        if (substr($webfingerQuery, 0, 1) !== '@') {
+            $webfingerQuery = '@'.$webfingerQuery;
         }
         $banned = InstanceService::getBannedDomains() ?? [];
         $domainBlocks = UserFilterService::domainBlocks($user->profile_id);
-        if($domainBlocks && count($domainBlocks)) {
+        if ($domainBlocks && count($domainBlocks)) {
             $banned = array_unique(
                 array_values(
                     array_merge($banned, $domainBlocks)
@@ -112,15 +110,15 @@ class SearchApiV2Service
             ->offset($offset)
             ->limit($limit)
             ->get()
-            ->filter(function($profile) use ($banned) {
+            ->filter(function ($profile) use ($banned) {
                 return in_array($profile->domain, $banned) == false;
             })
-            ->map(function($res) use($mastodonMode) {
+            ->map(function ($res) use ($mastodonMode) {
                 return $mastodonMode ?
                 AccountService::getMastodon($res['id']) :
                 AccountService::get($res['id']);
             })
-            ->filter(function($account) {
+            ->filter(function ($account) {
                 return $account && isset($account['id']);
             })
             ->values();
@@ -134,31 +132,31 @@ class SearchApiV2Service
         $q = $this->query->input('q');
         $limit = $this->query->input('limit') ?? 20;
         $offset = $this->query->input('offset') ?? 0;
-        $query = Str::startsWith($q, '#') ? '%' . substr($q, 1) . '%' : '%' . $q . '%';
+        $query = Str::startsWith($q, '#') ? substr($q, 1).'%' : $q;
         $operator = config('database.default') === 'pgsql' ? 'ilike' : 'like';
+
         return Hashtag::where('name', $operator, $query)
-            ->orWhere('slug', $operator, $query)
-            ->where(function($q) {
-                return $q->where('can_search', true)
-                        ->orWhereNull('can_search');
-            })
             ->orderByDesc('cached_count')
             ->offset($offset)
             ->limit($limit)
             ->get()
-            ->map(function($tag) use($mastodonMode) {
+            ->filter(function ($tag) {
+                return $tag->can_search != false;
+            })
+            ->map(function ($tag) use ($mastodonMode) {
                 $res = [
                     'name' => $tag->name,
-                    'url'  => $tag->url()
+                    'url' => $tag->url(),
                 ];
 
-                if(!$mastodonMode) {
+                if (! $mastodonMode) {
                     $res['history'] = [];
-                    $res['count'] = HashtagService::count($tag->id);
+                    $res['count'] = $tag->cached_count ?? 0;
                 }
 
                 return $res;
-            });
+            })
+            ->values();
     }
 
     protected function statuses()
@@ -175,7 +173,7 @@ class SearchApiV2Service
 
     protected function resolveQuery()
     {
-        $default =  [
+        $default = [
             'accounts' => [],
             'hashtags' => [],
             'statuses' => [],
@@ -185,73 +183,77 @@ class SearchApiV2Service
         $query = urldecode($this->query->input('q'));
         $banned = InstanceService::getBannedDomains();
         $domainBlocks = UserFilterService::domainBlocks($user->profile_id);
-        if($domainBlocks && count($domainBlocks)) {
+        if ($domainBlocks && count($domainBlocks)) {
             $banned = array_unique(
                 array_values(
                     array_merge($banned, $domainBlocks)
                 )
             );
         }
-        if(substr($query, 0, 1) === '@' && !Str::contains($query, '.')) {
+        if (substr($query, 0, 1) === '@' && ! Str::contains($query, '.')) {
             $default['accounts'] = $this->accounts(substr($query, 1));
+
             return $default;
         }
-        if(Helpers::validateLocalUrl($query)) {
-            if(Str::contains($query, '/p/') || Str::contains($query, 'i/web/post/')) {
+        if (Helpers::validateLocalUrl($query)) {
+            if (Str::contains($query, '/p/') || Str::contains($query, 'i/web/post/')) {
                 return $this->resolveLocalStatus();
-            }  else if(Str::contains($query, 'i/web/profile/')) {
+            } elseif (Str::contains($query, 'i/web/profile/')) {
                 return $this->resolveLocalProfileId();
             } else {
                 return $this->resolveLocalProfile();
             }
         } else {
-            if(!Helpers::validateUrl($query) && strpos($query, '@') == -1) {
+            if (! Helpers::validateUrl($query) && strpos($query, '@') == -1) {
                 return $default;
             }
 
-            if(!Str::startsWith($query, 'http') && Str::substrCount($query, '@') == 1 && strpos($query, '@') !== 0) {
+            if (! Str::startsWith($query, 'http') && Str::substrCount($query, '@') == 1 && strpos($query, '@') !== 0) {
                 try {
-                    $res = WebfingerService::lookup('@' . $query, $mastodonMode);
+                    $res = WebfingerService::lookup('@'.$query, $mastodonMode);
                 } catch (\Exception $e) {
                     return $default;
                 }
-                if($res && isset($res['id'], $res['url'])) {
+                if ($res && isset($res['id'], $res['url'])) {
                     $domain = strtolower(parse_url($res['url'], PHP_URL_HOST));
-                    if(in_array($domain, $banned)) {
+                    if (in_array($domain, $banned)) {
                         return $default;
                     }
                     $default['accounts'][] = $res;
+
                     return $default;
                 } else {
                     return $default;
                 }
             }
 
-            if(Str::substrCount($query, '@') == 2) {
+            if (Str::substrCount($query, '@') == 2) {
                 try {
                     $res = WebfingerService::lookup($query, $mastodonMode);
                 } catch (\Exception $e) {
                     return $default;
                 }
-                if($res && isset($res['id'])) {
+                if ($res && isset($res['id'])) {
                     $domain = strtolower(parse_url($res['url'], PHP_URL_HOST));
-                    if(in_array($domain, $banned)) {
+                    if (in_array($domain, $banned)) {
                         return $default;
                     }
                     $default['accounts'][] = $res;
+
                     return $default;
                 } else {
                     return $default;
                 }
             }
 
-            if($sid = Status::whereUri($query)->first()) {
+            if ($sid = Status::whereUri($query)->first()) {
                 $s = StatusService::get($sid->id, false);
-                if(!$s) {
+                if (! $s) {
                     return $default;
                 }
-                if(in_array($s['visibility'], ['public', 'unlisted'])) {
+                if (in_array($s['visibility'], ['public', 'unlisted'])) {
                     $default['statuses'][] = $s;
+
                     return $default;
                 }
             }
@@ -259,10 +261,10 @@ class SearchApiV2Service
             try {
                 $res = ActivityPubFetchService::get($query);
 
-                if($res) {
+                if ($res) {
                     $json = json_decode($res, true);
 
-                    if(!$json || !isset($json['@context']) || !isset($json['type']) || !in_array($json['type'], ['Note', 'Person'])) {
+                    if (! $json || ! isset($json['@context']) || ! isset($json['type']) || ! in_array($json['type'], ['Note', 'Person'])) {
                         return [
                             'accounts' => [],
                             'hashtags' => [],
@@ -270,38 +272,40 @@ class SearchApiV2Service
                         ];
                     }
 
-                    switch($json['type']) {
+                    switch ($json['type']) {
                         case 'Note':
                             $obj = Helpers::statusFetch($query);
-                            if(!$obj || !isset($obj['id'])) {
+                            if (! $obj || ! isset($obj['id'])) {
                                 return $default;
                             }
                             $note = $mastodonMode ?
                                 StatusService::getMastodon($obj['id'], false) :
                                 StatusService::get($obj['id'], false);
-                            if(!$note) {
+                            if (! $note) {
                                 return $default;
                             }
-                            if(!isset($note['visibility']) || !in_array($note['visibility'], ['public', 'unlisted'])) {
+                            if (! isset($note['visibility']) || ! in_array($note['visibility'], ['public', 'unlisted'])) {
                                 return $default;
                             }
                             $default['statuses'][] = $note;
+
                             return $default;
-                        break;
+                            break;
 
                         case 'Person':
                             $obj = Helpers::profileFetch($query);
-                            if(!$obj) {
+                            if (! $obj) {
                                 return $default;
                             }
-                            if(in_array($obj['domain'], $banned)) {
+                            if (in_array($obj['domain'], $banned)) {
                                 return $default;
                             }
                             $default['accounts'][] = $mastodonMode ?
                                 AccountService::getMastodon($obj['id'], true) :
                                 AccountService::get($obj['id'], true);
+
                             return $default;
-                        break;
+                            break;
 
                         default:
                             return [
@@ -309,7 +313,7 @@ class SearchApiV2Service
                                 'hashtags' => [],
                                 'statuses' => [],
                             ];
-                        break;
+                            break;
                     }
                 }
             } catch (\Exception $e) {
@@ -329,18 +333,18 @@ class SearchApiV2Service
         $query = urldecode($this->query->input('q'));
         $query = last(explode('/', parse_url($query, PHP_URL_PATH)));
         $status = StatusService::getMastodon($query, false);
-        if(!$status || !in_array($status['visibility'], ['public', 'unlisted'])) {
+        if (! $status || ! in_array($status['visibility'], ['public', 'unlisted'])) {
             return [
                 'accounts' => [],
                 'hashtags' => [],
-                'statuses' => []
+                'statuses' => [],
             ];
         }
 
         $res = [
             'accounts' => [],
             'hashtags' => [],
-            'statuses' => [$status]
+            'statuses' => [$status],
         ];
 
         return $res;
@@ -355,21 +359,22 @@ class SearchApiV2Service
             ->whereUsername($query)
             ->first();
 
-        if(!$profile) {
+        if (! $profile) {
             return [
                 'accounts' => [],
                 'hashtags' => [],
-                'statuses' => []
+                'statuses' => [],
             ];
         }
 
         $fractal = new Fractal\Manager();
         $fractal->setSerializer(new ArraySerializer());
         $resource = new Fractal\Resource\Item($profile, new AccountTransformer());
+
         return [
             'accounts' => [$fractal->createData($resource)->toArray()],
             'hashtags' => [],
-            'statuses' => []
+            'statuses' => [],
         ];
     }
 
@@ -380,22 +385,22 @@ class SearchApiV2Service
         $profile = Profile::whereNull('status')
             ->find($query);
 
-        if(!$profile) {
+        if (! $profile) {
             return [
                 'accounts' => [],
                 'hashtags' => [],
-                'statuses' => []
+                'statuses' => [],
             ];
         }
 
         $fractal = new Fractal\Manager();
         $fractal->setSerializer(new ArraySerializer());
         $resource = new Fractal\Resource\Item($profile, new AccountTransformer());
+
         return [
             'accounts' => [$fractal->createData($resource)->toArray()],
             'hashtags' => [],
-            'statuses' => []
+            'statuses' => [],
         ];
     }
-
 }

+ 90 - 89
app/Services/StatusHashtagService.php

@@ -2,96 +2,97 @@
 
 namespace App\Services;
 
-use Cache;
-use Illuminate\Support\Facades\Redis;
-use App\{Status, StatusHashtag};
-use App\Transformer\Api\StatusHashtagTransformer;
+use App\Hashtag;
+use App\Status;
+use App\StatusHashtag;
 use App\Transformer\Api\HashtagTransformer;
 use League\Fractal;
 use League\Fractal\Serializer\ArraySerializer;
-use League\Fractal\Pagination\IlluminatePaginatorAdapter;
-
-class StatusHashtagService {
-
-	const CACHE_KEY = 'pf:services:status-hashtag:collection:';
-
-	public static function get($id, $page = 1, $stop = 9)
-	{
-		if($page > 20) {
-			return [];
-		}
-
-		$pid = request()->user() ? request()->user()->profile_id : false;
-		$filtered = $pid ? UserFilterService::filters($pid) : [];
-
-		return StatusHashtag::whereHashtagId($id)
-			->whereStatusVisibility('public')
-			->skip($stop)
-			->latest()
-			->take(9)
-			->pluck('status_id')
-			->map(function ($i, $k) use ($id) {
-				return self::getStatus($i, $id);
-			})
-			->filter(function ($i) use($filtered) {
-				return isset($i['status']) &&
-				!empty($i['status']) && !in_array($i['status']['account']['id'], $filtered) &&
-				isset($i['status']['media_attachments']) &&
-				!empty($i['status']['media_attachments']);
-			})
-			->values();
-	}
-
-	public static function coldGet($id, $start = 0, $stop = 2000)
-	{
-		$stop = $stop > 2000 ? 2000 : $stop;
-		$ids = StatusHashtag::whereHashtagId($id)
-			->whereStatusVisibility('public')
-			->whereHas('media')
-			->latest()
-			->skip($start)
-			->take($stop)
-			->pluck('status_id');
-		foreach($ids as $key) {
-			self::set($id, $key);
-		}
-		return $ids;
-	}
-
-	public static function set($key, $val)
-	{
-		return 1;
-	}
-
-	public static function del($key)
-	{
-		return 1;
-	}
-
-	public static function count($id)
-	{
-		$key = 'pf:services:status-hashtag:count:' . $id;
-		$ttl = now()->addMinutes(5);
-		return Cache::remember($key, $ttl, function() use($id) {
-			return StatusHashtag::whereHashtagId($id)->has('media')->count();
-		});
-	}
-
-	public static function getStatus($statusId, $hashtagId)
-	{
-		return ['status' => StatusService::get($statusId)];
-	}
-
-	public static function statusTags($statusId)
-	{
-		$status = Status::with('hashtags')->find($statusId);
-		if(!$status) {
-			return [];
-		}
-
-		$fractal = new Fractal\Manager();
-		$fractal->setSerializer(new ArraySerializer());
-		$resource = new Fractal\Resource\Collection($status->hashtags, new HashtagTransformer());
-		return $fractal->createData($resource)->toArray();
-	}
+
+class StatusHashtagService
+{
+    const CACHE_KEY = 'pf:services:status-hashtag:collection:';
+
+    public static function get($id, $page = 1, $stop = 9)
+    {
+        if ($page > 20) {
+            return [];
+        }
+
+        $pid = request()->user() ? request()->user()->profile_id : false;
+        $filtered = $pid ? UserFilterService::filters($pid) : [];
+
+        return StatusHashtag::whereHashtagId($id)
+            ->whereStatusVisibility('public')
+            ->skip($stop)
+            ->latest()
+            ->take(9)
+            ->pluck('status_id')
+            ->map(function ($i, $k) use ($id) {
+                return self::getStatus($i, $id);
+            })
+            ->filter(function ($i) use ($filtered) {
+                return isset($i['status']) &&
+                ! empty($i['status']) && ! in_array($i['status']['account']['id'], $filtered) &&
+                isset($i['status']['media_attachments']) &&
+                ! empty($i['status']['media_attachments']);
+            })
+            ->values();
+    }
+
+    public static function coldGet($id, $start = 0, $stop = 2000)
+    {
+        $stop = $stop > 2000 ? 2000 : $stop;
+        $ids = StatusHashtag::whereHashtagId($id)
+            ->whereStatusVisibility('public')
+            ->whereHas('media')
+            ->latest()
+            ->skip($start)
+            ->take($stop)
+            ->pluck('status_id');
+        foreach ($ids as $key) {
+            self::set($id, $key);
+        }
+
+        return $ids;
+    }
+
+    public static function set($key, $val)
+    {
+        return 1;
+    }
+
+    public static function del($key)
+    {
+        return 1;
+    }
+
+    public static function count($id)
+    {
+        $cc = Hashtag::find($id);
+        if (! $cc) {
+            return 0;
+        }
+
+        return $cc->cached_count ?? 0;
+    }
+
+    public static function getStatus($statusId, $hashtagId)
+    {
+        return ['status' => StatusService::get($statusId)];
+    }
+
+    public static function statusTags($statusId)
+    {
+        $status = Status::with('hashtags')->find($statusId);
+        if (! $status) {
+            return [];
+        }
+
+        $fractal = new Fractal\Manager();
+        $fractal->setSerializer(new ArraySerializer());
+        $resource = new Fractal\Resource\Collection($status->hashtags, new HashtagTransformer());
+
+        return $fractal->createData($resource)->toArray();
+    }
 }

二進制
public/js/manifest.js


二進制
public/js/post.chunk.5ff16664f9adb901.js


二進制
public/js/post.chunk.9184101a2b809af1.js


+ 0 - 0
public/js/post.chunk.5ff16664f9adb901.js.LICENSE.txt → public/js/post.chunk.9184101a2b809af1.js.LICENSE.txt


二進制
public/mix-manifest.json


+ 2 - 15
resources/assets/components/Post.vue

@@ -171,7 +171,7 @@
 			}
 		},
 
-		beforeMount() {
+		created() {
 			this.init();
 		},
 
@@ -181,20 +181,7 @@
 
 		methods: {
 			init() {
-				if(this.cachedStatus && this.cachedProfile) {
-					this.post = this.cachedStatus;
-					this.media = this.post.media_attachments;
-					this.profile = this.post.account;
-					this.user = this.cachedProfile;
-					if(this.post.in_reply_to_id) {
-						this.fetchReply();
-					} else {
-						this.isReply = false;
-						this.fetchRelationship();
-					}
-				} else {
-					this.fetchSelf();
-				}
+				this.fetchSelf();
 			},
 
 			fetchSelf() {