Limit anonymous community activity queries to the latest page.
Skip unused activity sources and historical over-fetching for guests while keeping authenticated feeds filterable.
This commit is contained in:
@@ -33,8 +33,10 @@ final class CommunityActivityService
|
|||||||
public function getFeed(?User $viewer, string $filter = self::FILTER_ALL, int $page = 1, int $perPage = self::DEFAULT_PER_PAGE, ?int $actorUserId = null): array
|
public function getFeed(?User $viewer, string $filter = self::FILTER_ALL, int $page = 1, int $perPage = self::DEFAULT_PER_PAGE, ?int $actorUserId = null): array
|
||||||
{
|
{
|
||||||
$normalizedFilter = $this->normalizeFilter($filter);
|
$normalizedFilter = $this->normalizeFilter($filter);
|
||||||
$resolvedPage = max(1, $page);
|
$resolvedPage = $viewer ? max(1, $page) : 1;
|
||||||
$resolvedPerPage = max(1, min(50, $perPage));
|
$resolvedPerPage = $viewer
|
||||||
|
? max(1, min(50, $perPage))
|
||||||
|
: 20;
|
||||||
|
|
||||||
$cacheKey = sprintf(
|
$cacheKey = sprintf(
|
||||||
'community_activity:%s:%d:%d:%d:%d',
|
'community_activity:%s:%d:%d:%d:%d',
|
||||||
@@ -68,20 +70,55 @@ final class CommunityActivityService
|
|||||||
|
|
||||||
private function buildFeed(?User $viewer, string $filter, int $page, int $perPage, ?int $actorUserId): array
|
private function buildFeed(?User $viewer, string $filter, int $page, int $perPage, ?int $actorUserId): array
|
||||||
{
|
{
|
||||||
$sourceLimit = max(80, $page * $perPage * 6);
|
// Guests only receive the latest page. Since every source is ordered
|
||||||
|
// newest-first, one page per source is sufficient to produce the
|
||||||
|
// globally newest page after merging, while avoiding historical
|
||||||
|
// over-fetching for anonymous traffic.
|
||||||
|
$sourceLimit = $viewer === null
|
||||||
|
? $perPage
|
||||||
|
: max(80, $page * $perPage * 6);
|
||||||
$followingIds = $filter === self::FILTER_FOLLOWING && $viewer
|
$followingIds = $filter === self::FILTER_FOLLOWING && $viewer
|
||||||
? $viewer->following()->pluck('users.id')->map(fn ($id) => (int) $id)->all()
|
? $viewer->following()->pluck('users.id')->map(fn ($id) => (int) $id)->all()
|
||||||
: [];
|
: [];
|
||||||
|
$actorIds = match ($filter) {
|
||||||
|
self::FILTER_FOLLOWING => $followingIds,
|
||||||
|
self::FILTER_MY => $viewer ? [(int) $viewer->id] : [],
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
|
||||||
$commentModels = $this->fetchCommentModels($sourceLimit, repliesOnly: false);
|
if ($actorUserId !== null) {
|
||||||
$replyModels = $this->fetchCommentModels($sourceLimit, repliesOnly: true);
|
$actorIds = $actorIds === null
|
||||||
$reactionModels = $this->fetchReactionModels($sourceLimit);
|
? [(int) $actorUserId]
|
||||||
$recordedActivities = $this->fetchRecordedActivities($sourceLimit);
|
: array_values(array_intersect($actorIds, [(int) $actorUserId]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A comments/replies feed can only contain its matching comment type.
|
||||||
|
// Avoid loading and hydrating the other activity sources only to discard
|
||||||
|
// them in the merge filter below. Actor-scoped filters also constrain
|
||||||
|
// each source query while preserving the final in-memory safety filter.
|
||||||
|
$commentModels = in_array($filter, [self::FILTER_ALL, self::FILTER_COMMENTS, self::FILTER_FOLLOWING, self::FILTER_MY], true)
|
||||||
|
? $this->fetchCommentModels($sourceLimit, repliesOnly: false, actorIds: $actorIds)
|
||||||
|
: collect();
|
||||||
|
$replyModels = in_array($filter, [self::FILTER_ALL, self::FILTER_REPLIES, self::FILTER_FOLLOWING, self::FILTER_MY], true)
|
||||||
|
? $this->fetchCommentModels($sourceLimit, repliesOnly: true, actorIds: $actorIds)
|
||||||
|
: collect();
|
||||||
|
$reactionModels = in_array($filter, [self::FILTER_ALL, self::FILTER_FOLLOWING, self::FILTER_MY], true)
|
||||||
|
? $this->fetchReactionModels($sourceLimit, actorIds: $actorIds)
|
||||||
|
: collect();
|
||||||
|
$recordedActivities = in_array($filter, [self::FILTER_ALL, self::FILTER_FOLLOWING, self::FILTER_MY], true)
|
||||||
|
? $this->fetchRecordedActivities($sourceLimit, actorIds: $actorIds)
|
||||||
|
: collect();
|
||||||
|
|
||||||
$commentActivities = $commentModels->map(fn (ArtworkComment $comment) => $this->mapCommentActivity($comment, 'comment'));
|
$commentActivities = $commentModels->map(fn (ArtworkComment $comment) => $this->mapCommentActivity($comment, 'comment'));
|
||||||
$replyActivities = $replyModels->map(fn (ArtworkComment $comment) => $this->mapCommentActivity($comment, 'reply'));
|
$replyActivities = $replyModels->map(fn (ArtworkComment $comment) => $this->mapCommentActivity($comment, 'reply'));
|
||||||
$reactionActivities = $reactionModels->map(fn (CommentReaction $reaction) => $this->mapReactionActivity($reaction));
|
$reactionActivities = $reactionModels->map(fn (CommentReaction $reaction) => $this->mapReactionActivity($reaction));
|
||||||
$mentionActivities = $this->fetchMentionActivities($sourceLimit);
|
$mentionActivities = in_array($filter, [self::FILTER_ALL, self::FILTER_FOLLOWING, self::FILTER_MY], true)
|
||||||
|
? $this->fetchMentionActivities(
|
||||||
|
$sourceLimit,
|
||||||
|
actorIds: $actorIds,
|
||||||
|
mentionedUserId: $filter === self::FILTER_MY ? $viewer?->id : null,
|
||||||
|
)
|
||||||
|
: collect();
|
||||||
|
|
||||||
$merged = $recordedActivities
|
$merged = $recordedActivities
|
||||||
->concat($commentActivities)
|
->concat($commentActivities)
|
||||||
@@ -140,7 +177,7 @@ final class CommunityActivityService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchRecordedActivities(int $limit): Collection
|
private function fetchRecordedActivities(int $limit, ?array $actorIds = null): Collection
|
||||||
{
|
{
|
||||||
$events = ActivityEvent::query()
|
$events = ActivityEvent::query()
|
||||||
->select(['id', 'actor_id', 'type', 'target_type', 'target_id', 'meta', 'created_at'])
|
->select(['id', 'actor_id', 'type', 'target_type', 'target_id', 'meta', 'created_at'])
|
||||||
@@ -153,6 +190,7 @@ final class CommunityActivityService
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
->whereHas('actor', fn ($query) => $query->where('is_active', true)->whereNull('deleted_at'))
|
->whereHas('actor', fn ($query) => $query->where('is_active', true)->whereNull('deleted_at'))
|
||||||
|
->when($actorIds !== null, fn ($query) => $query->whereIn('actor_id', $actorIds))
|
||||||
->latest('created_at')
|
->latest('created_at')
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get();
|
||||||
@@ -223,7 +261,7 @@ final class CommunityActivityService
|
|||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchCommentModels(int $limit, bool $repliesOnly): Collection
|
private function fetchCommentModels(int $limit, bool $repliesOnly, ?array $actorIds = null): Collection
|
||||||
{
|
{
|
||||||
return ArtworkComment::query()
|
return ArtworkComment::query()
|
||||||
->select([
|
->select([
|
||||||
@@ -250,6 +288,7 @@ final class CommunityActivityService
|
|||||||
])
|
])
|
||||||
->where('is_approved', true)
|
->where('is_approved', true)
|
||||||
->whereNull('deleted_at')
|
->whereNull('deleted_at')
|
||||||
|
->when($actorIds !== null, fn ($query) => $query->whereIn('user_id', $actorIds))
|
||||||
->when($repliesOnly, fn ($query) => $query->whereNotNull('parent_id'), fn ($query) => $query->whereNull('parent_id'))
|
->when($repliesOnly, fn ($query) => $query->whereNotNull('parent_id'), fn ($query) => $query->whereNull('parent_id'))
|
||||||
->whereHas('user', function ($query) {
|
->whereHas('user', function ($query) {
|
||||||
$query->where('is_active', true)->whereNull('deleted_at');
|
$query->where('is_active', true)->whereNull('deleted_at');
|
||||||
@@ -262,7 +301,7 @@ final class CommunityActivityService
|
|||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchReactionModels(int $limit): Collection
|
private function fetchReactionModels(int $limit, ?array $actorIds = null): Collection
|
||||||
{
|
{
|
||||||
return CommentReaction::query()
|
return CommentReaction::query()
|
||||||
->select(['id', 'comment_id', 'user_id', 'reaction', 'created_at'])
|
->select(['id', 'comment_id', 'user_id', 'reaction', 'created_at'])
|
||||||
@@ -292,6 +331,7 @@ final class CommunityActivityService
|
|||||||
->whereHas('user', function ($query) {
|
->whereHas('user', function ($query) {
|
||||||
$query->where('is_active', true)->whereNull('deleted_at');
|
$query->where('is_active', true)->whereNull('deleted_at');
|
||||||
})
|
})
|
||||||
|
->when($actorIds !== null, fn ($query) => $query->whereIn('user_id', $actorIds))
|
||||||
->whereHas('comment', function ($query) {
|
->whereHas('comment', function ($query) {
|
||||||
$query
|
$query
|
||||||
->where('is_approved', true)
|
->where('is_approved', true)
|
||||||
@@ -395,7 +435,7 @@ final class CommunityActivityService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchMentionActivities(int $limit): Collection
|
private function fetchMentionActivities(int $limit, ?array $actorIds = null, ?int $mentionedUserId = null): Collection
|
||||||
{
|
{
|
||||||
if (! Schema::hasTable('user_mentions')) {
|
if (! Schema::hasTable('user_mentions')) {
|
||||||
return collect();
|
return collect();
|
||||||
@@ -403,6 +443,15 @@ final class CommunityActivityService
|
|||||||
|
|
||||||
return UserMention::query()
|
return UserMention::query()
|
||||||
->select(['id', 'user_id', 'mentioned_user_id', 'artwork_id', 'comment_id', 'created_at'])
|
->select(['id', 'user_id', 'mentioned_user_id', 'artwork_id', 'comment_id', 'created_at'])
|
||||||
|
->when($actorIds !== null, function ($query) use ($actorIds, $mentionedUserId): void {
|
||||||
|
$query->where(function ($scope) use ($actorIds, $mentionedUserId): void {
|
||||||
|
$scope->whereIn('user_id', $actorIds);
|
||||||
|
|
||||||
|
if ($mentionedUserId !== null) {
|
||||||
|
$scope->orWhere('mentioned_user_id', $mentionedUserId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
->with([
|
->with([
|
||||||
'actor' => function ($query) {
|
'actor' => function ($query) {
|
||||||
$query
|
$query
|
||||||
|
|||||||
Reference in New Issue
Block a user